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

import java.io.IOException;
import java.io.File;
import java.util.*;
import java.awt.event.ActionEvent;
import javax.swing.*;

import org.openide.NotifyDescriptor;
import org.openide.awt.UndoRedo;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataShadow;
import org.openide.nodes.Node;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.Cancellable;
import org.openide.util.Lookup;
import org.openide.util.actions.NodeAction;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;

import org.netbeans.api.diff.*;
import org.netbeans.api.project.Project;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.modules.diff.builtin.DefaultDiff;
import org.netbeans.modules.diff.builtin.SingleDiffPanel;
import org.netbeans.modules.diff.options.AccessibleJFileChooser;
import org.openide.DialogDisplayer;
import org.openide.ErrorManager;

/**
 * Diff Action. It gets the default diff visualizer and diff provider if needed
 * and display the diff visual representation of two files selected in the IDE.
 *
 * @author  Martin Entlicher
 */
public class DiffAction extends NodeAction {

    /** Creates new DiffAction */
    public DiffAction() {
        putValue("noIconInMenu", Boolean.TRUE); // NOI18N
    }

    private static boolean diffAvailable = true;
    
    private class DiffActionImpl extends AbstractAction {
        
        private final Node [] nodes;

        private DiffActionImpl(Lookup context) {
            Collection<? extends Node> nodez = context.lookup(new Lookup.Template<Node>(Node.class)).allInstances();
            nodes = nodez.toArray(new Node[nodez.size()]);
            if (nodes.length == 1) {
                putValue(Action.NAME, NbBundle.getMessage(DiffAction.class, "CTL_DiffToActionName"));
            } else {
                putValue(Action.NAME, getName());                
            }
        }

        public void actionPerformed(ActionEvent e) {
            performAction(nodes);
        }

        public boolean isEnabled() {
            return DiffAction.this.enable(nodes);
        }
    }
    
    public Action createContextAwareInstance(Lookup actionContext) {
        return new DiffActionImpl(actionContext);
    }

    public String getName() {
        return NbBundle.getMessage(DiffAction.class, "CTL_DiffActionName");
    }
    
    static FileObject getFileFromNode(Node node) {
        FileObject fo = (FileObject) node.getLookup().lookup(FileObject.class);
        if (fo == null) {
            Project p = (Project) node.getLookup().lookup(Project.class);
            if (p != null) return p.getProjectDirectory();

            DataObject dobj = (DataObject) node.getCookie(DataObject.class);
            if (dobj instanceof DataShadow) {
                dobj = ((DataShadow) dobj).getOriginal();
            }
            if (dobj != null) {
                fo = dobj.getPrimaryFile();
            }
        }
        return fo;
    }
    
    public boolean enable(Node[] nodes) {
        //System.out.println("DiffAction.enable() = "+(nodes.length == 2));
        if (!diffAvailable) {
            return false;
        }
        if (nodes.length == 2) {
            FileObject fo1 = getFileFromNode(nodes[0]);
            FileObject fo2 = getFileFromNode(nodes[1]);
            if (fo1 != null && fo2 != null) {
                if (fo1.isData() && fo2.isData()) {
                    return true;
                }
            }
        } else if (nodes.length == 1) {
            FileObject fo1 = getFileFromNode(nodes[0]);
            if (fo1 != null) {
                if (fo1.isData()) {
                    return true;
                }
            }
        }
        return false;
    }
    
    /**
     * This action should not be run in AWT thread, because it opens streams
     * to files.
     * @return true not to run in AWT thread!
     */
    protected boolean asynchronous() {
        return false;
    }
    
    public void performAction(Node[] nodes) {
        ArrayList<FileObject> fos = new ArrayList<FileObject>();
        for (int i = 0; i < nodes.length; i++) {
            FileObject fo = getFileFromNode(nodes[i]);
            if (fo != null) {
                fos.add(fo);
            }
        }
        if (fos.size() < 1) return ;
        final FileObject fo1 = fos.get(0);
        final FileObject fo2;
        if (fos.size() > 1) {
            fo2 = fos.get(1);
        } else {
            fo2 = promptForFileobject(fo1);
            if (fo2 == null) return;
        }
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                performAction(fo1, fo2, null);
            }
        });
    }

    private FileObject promptForFileobject(FileObject peer) {
        String path = DiffModuleConfig.getDefault().getPreferences().get("diffToLatestFolder", peer.getParent().getPath());
        File latestPath = FileUtil.normalizeFile(new File(path));
        JFileChooser fileChooser = new AccessibleJFileChooser(NbBundle.getMessage(DiffAction.class, "ACSD_BrowseDiffToFile"), latestPath); // NOI18N
        fileChooser.setDialogTitle(NbBundle.getMessage(DiffAction.class, "DiffTo_BrowseFile_Title", peer.getName())); // NOI18N
        fileChooser.setMultiSelectionEnabled(false);
        fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        EditorBufferSelectorPanel editorSelector = new EditorBufferSelectorPanel(fileChooser, peer);
        fileChooser.setAccessory(editorSelector);

        int result = fileChooser.showDialog(WindowManager.getDefault().getMainWindow(), NbBundle.getMessage(DiffAction.class, "DiffTo_BrowseFile_OK")); // NOI18N
        if (result != JFileChooser.APPROVE_OPTION) return null;

        FileObject userSelectedFo = editorSelector.getSelectedEditorFile();
        if (userSelectedFo != null) {
            return userSelectedFo;
        }
        
        File f = fileChooser.getSelectedFile();
        if (f != null) {
            File file = f.getAbsoluteFile();
            DiffModuleConfig.getDefault().getPreferences().put("diffToLatestFolder", file.getParent());
            return FileUtil.toFileObject(f);
        }
        return null;
    }
    
    /**
     * Shows the diff between two FileObject objects.
     * This is expected not to be called in AWT thread.
     * @param type Use the type of that FileObject to load both files.
     */
    static void performAction(FileObject fo1, FileObject fo2, FileObject type) {
        //System.out.println("performAction("+fo1+", "+fo2+")");
        //doDiff(fo1, fo2);
        Diff diff = Diff.getDefault();
        //System.out.println("dv = "+dv);
        if (diff == null) {
            DialogDisplayer.getDefault().notify(
                new NotifyDescriptor.Message(NbBundle.getMessage(DiffAction.class,
                    "MSG_NoDiffVisualizer")));
            diffAvailable = false;
            return ;
        }
        SingleDiffPanel sdp = null;
        try {
            final Thread victim = Thread.currentThread();
            Cancellable killer = new Cancellable() {
                public boolean cancel() {
                    victim.interrupt();
                    return true;
                }
            };
            String name = NbBundle.getMessage(DiffAction.class, "BK0001");
            try (ProgressHandle ph = ProgressHandle.createHandle(name, killer)) {
                ph.start();
                sdp = new SingleDiffPanel(fo1, fo2, type);
            }
        } catch (IOException ioex) {
            ErrorManager.getDefault().notify(ioex);
            return ;
        }
        //System.out.println("tp = "+tp);
        if (sdp != null) {
            final SingleDiffPanel fsdp = sdp;
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    TopComponent dtc = new DefaultDiff.DiffTopComponent(fsdp) {
                        @Override
                        protected void componentActivated() {
                            super.componentActivated();
                            fsdp.requestActive();
                        }

                        @Override
                        protected void componentClosed() {
                            super.componentClosed(); //To change body of generated methods, choose Tools | Templates.
                            fsdp.closed();
                        }
                        
                        @Override
                        public UndoRedo getUndoRedo() {
                            return fsdp.getUndoRedo();
                        }
                    };
                    fsdp.putClientProperty(TopComponent.class, dtc);
                    fsdp.activateNodes();
                    dtc.open();
                    dtc.requestActive();
                }
            });
        }
    }
    
    public HelpCtx getHelpCtx() {
        return new HelpCtx(DiffAction.class);
    }

}
