/*  Copyright (C) 2003-2011 JabRef contributors.
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package net.sf.jabref.external;

import com.jgoodies.forms.builder.ButtonBarBuilder;
import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.layout.FormLayout;
import net.sf.jabref.*;
import net.sf.jabref.gui.FileListEntry;
import net.sf.jabref.gui.FileListEntryEditor;
import net.sf.jabref.gui.FileListTableModel;
import net.sf.jabref.undo.NamedCompound;
import net.sf.jabref.undo.UndoableFieldChange;

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

/**
 * This action goes through all selected entries in the BasePanel, and attempts to autoset the
 * given external file (pdf, ps, ...) based on the same algorithm used for the "Auto" button in
 * EntryEditor.
 */
public class SynchronizeFileField extends AbstractWorker {

    private String fieldName = GUIGlobals.FILE_FIELD;
    private BasePanel panel;
    private BibtexEntry[] sel = null;
    private SynchronizeFileField.OptionsDialog optDiag = null;

    Object[] brokenLinkOptions =
            {Globals.lang("Ignore"), Globals.lang("Assign new file"), Globals.lang("Remove link"),
                    Globals.lang("Remove all broken links"),
                    Globals.lang("Quit synchronization")};

    private boolean goOn = true, autoSet = true, checkExisting = true;

    private int brokenLinks = 0, entriesChangedCount = 0;

    public SynchronizeFileField(BasePanel panel) {
        this.panel = panel;
    }

    public void init() {
        Collection<BibtexEntry> col = panel.database().getEntries();
        goOn = true;
        sel = new BibtexEntry[col.size()];
        sel = col.toArray(sel);

        // Ask about rules for the operation:
        if (optDiag == null)
            optDiag = new SynchronizeFileField.OptionsDialog(panel.frame(), panel.metaData(), fieldName);
        Util.placeDialog(optDiag, panel.frame());
        optDiag.setVisible(true);
        if (optDiag.canceled()) {
            goOn = false;
            return;
        }
        autoSet = !optDiag.autoSetNone.isSelected();
        checkExisting = optDiag.checkLinks.isSelected();
        
        panel.output(Globals.lang("Synchronizing %0 links...", fieldName.toUpperCase()));
    }

    public void run() {
        if (!goOn) {
            panel.output(Globals.lang("No entries selected."));
            return;
        }
        panel.frame().setProgressBarValue(0);
        panel.frame().setProgressBarVisible(true);
        int weightAutoSet = 10; // autoSet takes 10 (?) times longer than checkExisting
        int progressBarMax = (autoSet ? weightAutoSet * sel.length : 0)
                + (checkExisting ? sel.length : 0);
        panel.frame().setProgressBarMaximum(progressBarMax);
        int progress = 0;
        brokenLinks = 0;
        final NamedCompound ce = new NamedCompound(Globals.lang("Autoset %0 field", fieldName));

        //final OpenFileFilter off = Util.getFileFilterForField(fieldName);

        //ExternalFilePanel extPan = new ExternalFilePanel(fieldName, panel.metaData(), null, null, off);
        //FieldTextField editor = new FieldTextField(fieldName, "", false);

        Set<BibtexEntry> changedEntries = new HashSet<BibtexEntry>();

        // First we try to autoset fields
        if (autoSet) {
            Collection<BibtexEntry> entries = new ArrayList<BibtexEntry>();
            for (int i = 0; i < sel.length; i++) {
                entries.add(sel[i]);
            }

            // Start the autosetting process:                
            Thread t = Util.autoSetLinks(entries, ce, changedEntries, null, panel.metaData(), null, null);
            // Wait for the autosetting to finish:
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            /*
                progress += weightAutoSet;
                panel.frame().setProgressBarValue(progress);

                Object old = sel[i].getField(fieldName);
                FileListTableModel tableModel = new FileListTableModel();
                if (old != null)
                    tableModel.setContent((String)old);
                Thread t = FileListEditor.autoSetLinks(sel[i], tableModel, null, null);

                if (!tableModel.getStringRepresentation().equals(old)) {
                    String toSet = tableModel.getStringRepresentation();
                    if (toSet.length() == 0)
                        toSet = null;
                    ce.addEdit(new UndoableFieldChange(sel[i], fieldName, old, toSet));
                    sel[i].setField(fieldName, toSet);
                    entriesChanged++;
                }
            }    */

        }
        progress += sel.length*weightAutoSet;
        panel.frame().setProgressBarValue(progress);
        //System.out.println("Done setting");
        // The following loop checks all external links that are already set.
        if (checkExisting) {
            boolean removeAllBroken = false;
            mainLoop:
            for (int i = 0; i < sel.length; i++) {
                panel.frame().setProgressBarValue(progress++);
                final String old = sel[i].getField(fieldName);
                // Check if a extension is set:
                if ((old != null) && !old.equals("")) {
                    FileListTableModel tableModel = new FileListTableModel();
                    tableModel.setContentDontGuessTypes(old);

                    // We need to specify which directories to search in for Util.expandFilename:
                    String[] dirsS = panel.metaData().getFileDirectory(GUIGlobals.FILE_FIELD);
                    ArrayList<File> dirs = new ArrayList<File>();
                    for (int k=0; k<dirsS.length; k++)
                        dirs.add(new File(dirsS[k]));

                    for (int j=0; j<tableModel.getRowCount(); j++) {
                        FileListEntry flEntry = tableModel.getEntry(j);
                        // See if the link looks like an URL:
                        boolean httpLink = flEntry.getLink().toLowerCase().startsWith("http");
                        if (httpLink)
                            continue; // Don't check the remote file.
                        // TODO: should there be an option to check remote links?

                        // A variable to keep track of whether this link gets deleted:
                        boolean deleted = false;

                        // Get an absolute path representation:
                        File file = Util.expandFilename(flEntry.getLink(), dirsS);
                        if ((file == null) || !file.exists()) {
                            int answer;
                            if (!removeAllBroken) {
                                answer = JOptionPane.showOptionDialog(panel.frame(),
                                    Globals.lang("<HTML>Could not find file '%0'<BR>linked from entry '%1'</HTML>",
                                            new String[]{flEntry.getLink(), sel[i].getCiteKey()}),
                                    Globals.lang("Broken link"),
                                    JOptionPane.YES_NO_CANCEL_OPTION,
                                    JOptionPane.QUESTION_MESSAGE, null, brokenLinkOptions, brokenLinkOptions[0]);
                            }
                            else {
                                answer = 2; // We should delete this link.
                            }
                            switch (answer) {
                                case 1:
                                    // Assign new file.
                                    FileListEntryEditor flEditor = new FileListEntryEditor
                                            (panel.frame(), flEntry, false, true, panel.metaData());
                                    flEditor.setVisible(true, true);
                                    break;
                                case 2:
                                    // Clear field:
                                    tableModel.removeEntry(j);
                                    deleted = true; // Make sure we don't investigate this link further.
                                    j--; // Step back in the iteration, because we removed an entry.
                                    break;
                                case 3:
                                    // Clear field:
                                    tableModel.removeEntry(j);
                                    deleted = true; // Make sure we don't investigate this link further.
                                    j--; // Step back in the iteration, because we removed an entry.
                                    removeAllBroken = true; // Notify for further cases.
                                    break;
                                case 4:
                                    // Cancel
                                    break mainLoop;
                            }
                            brokenLinks++;
                        }

                        // Unless we deleted this link, see if its file type is recognized:
                        if (!deleted && (flEntry.getType() instanceof UnknownExternalFileType)) {
                            String[] options = new String[]
                                    {Globals.lang("Define '%0'", flEntry.getType().getName()),
                                    Globals.lang("Change file type"), Globals.lang("Cancel")};
                            String defOption = options[0];
                            int answer = JOptionPane.showOptionDialog(panel.frame(), Globals.lang("One or more file links are of the type '%0', which is undefined. What do you want to do?",
                                    flEntry.getType().getName()),
                                    Globals.lang("Undefined file type"), JOptionPane.YES_NO_CANCEL_OPTION,
                                    JOptionPane.QUESTION_MESSAGE, null, options, defOption);
                            if (answer == JOptionPane.CANCEL_OPTION) {
                                // User doesn't want to handle this unknown link type.
                            }
                            else if (answer == JOptionPane.YES_OPTION) {
                                // User wants to define the new file type. Show the dialog:
                                ExternalFileType newType = new ExternalFileType(flEntry.getType().getName(), "", "", "", "new");
                                ExternalFileTypeEntryEditor editor = new ExternalFileTypeEntryEditor(panel.frame(), newType);
                                editor.setVisible(true);
                                if (editor.okPressed()) {
                                    // Get the old list of types, add this one, and update the list in prefs:
                                    java.util.List<ExternalFileType> fileTypes = new ArrayList<ExternalFileType>();
                                    ExternalFileType[] oldTypes = Globals.prefs.getExternalFileTypeSelection();
                                    for (int k = 0; k < oldTypes.length; k++) {
                                        fileTypes.add(oldTypes[k]);
                                    }
                                    fileTypes.add(newType);
                                    Collections.sort(fileTypes);
                                    Globals.prefs.setExternalFileTypes(fileTypes);
                                    panel.mainTable.repaint();
                                }
                            }
                            else {
                                // User wants to change the type of this link.
                                // First get a model of all file links for this entry:
                                FileListEntryEditor editor = new FileListEntryEditor
                                        (panel.frame(), flEntry, false, true, panel.metaData());
                                editor.setVisible(true, false);
                            }
                        }
                    }

                    if (!tableModel.getStringRepresentation().equals(old)) {
                        // The table has been modified. Store the change:
                        String toSet = tableModel.getStringRepresentation();
                        if (toSet.length() == 0)
                            toSet = null;
                        ce.addEdit(new UndoableFieldChange(sel[i], fieldName, old,
                                toSet));
                        sel[i].setField(fieldName, toSet);
                        changedEntries.add(sel[i]);
                        //System.out.println("Changed to: "+tableModel.getStringRepresentation());
                    }


                }
            }
        }

	//for (BibtexEntry entr : changedEntries)
	//    System.out.println(entr.getCiteKey());
        if (!changedEntries.isEmpty()) {
            // Add the undo edit:
            ce.end();
            panel.undoManager.addEdit(ce);
            panel.markBaseChanged();
        }
    }


    public void update() {
        if (!goOn)
            return;

        panel.output(Globals.lang("Finished synchronizing %0 links. Entries changed%c %1.",
                new String[]{fieldName.toUpperCase(), String.valueOf(entriesChangedCount)}));
        panel.frame().setProgressBarVisible(false);
        if (entriesChangedCount > 0) {
            panel.markBaseChanged();
        }
    }

    static class OptionsDialog extends JDialog {
        JRadioButton autoSetUnset, autoSetAll, autoSetNone;
        JCheckBox checkLinks;
        JButton ok = new JButton(Globals.lang("Ok")),
                cancel = new JButton(Globals.lang("Cancel"));
        JLabel description;
        private boolean canceled = true;
        private MetaData metaData;

        public OptionsDialog(JFrame parent, MetaData metaData, String fieldName) {
            super(parent, Globals.lang("Synchronize %0 links", fieldName.toUpperCase()), true);
            this.metaData = metaData;
            final String fn = Globals.lang("file");
            ok.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    canceled = false;
                    dispose();
                }
            });

            Action closeAction = new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    dispose();
                }
            };


            cancel.addActionListener(closeAction);

            InputMap im = cancel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
            ActionMap am = cancel.getActionMap();
            im.put(Globals.prefs.getKey("Close dialog"), "close");
            am.put("close", closeAction);

            autoSetUnset = new JRadioButton(Globals.lang("Autoset %0 links. Do not overwrite existing links.", fn), true);
            autoSetAll = new JRadioButton(Globals.lang("Autoset %0 links. Allow overwriting existing links.", fn), false);
            autoSetNone = new JRadioButton(Globals.lang("Do not autoset"), false);
            checkLinks = new JCheckBox(Globals.lang("Check existing %0 links", fn), true);
            ButtonGroup bg = new ButtonGroup();
            bg.add(autoSetUnset);
            bg.add(autoSetNone);
            bg.add(autoSetAll);
            FormLayout layout = new FormLayout("fill:pref", "");
            DefaultFormBuilder builder = new DefaultFormBuilder(layout);
            description = new JLabel("<HTML>" +
                    Globals.lang(//"This function helps you keep your external %0 links up-to-date." +
                            "Attempt to autoset %0 links for your entries. Autoset works if "
                                    + "a %0 file in your %0 directory or a subdirectory<BR>is named identically to an entry's BibTeX key, plus extension.", fn)
                    + "</HTML>");
            //            name.setVerticalAlignment(JLabel.TOP);
            builder.appendSeparator(Globals.lang("Autoset"));
            builder.append(description);
            builder.nextLine();
            builder.append(autoSetUnset);
            builder.nextLine();
            builder.append(autoSetAll);
            builder.nextLine();
            builder.append(autoSetNone);
            builder.nextLine();
            builder.appendSeparator(Globals.lang("Check links"));

            description = new JLabel("<HTML>" +
                    Globals.lang("This makes JabRef look up each %0 extension and check if the file exists. If not, you will be given options<BR>to resolve the problem.", fn)
                    + "</HTML>");
            builder.append(description);
            builder.nextLine();
            builder.append(checkLinks);
            builder.nextLine();
            builder.appendSeparator();


            JPanel main = builder.getPanel();
            main.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

            ButtonBarBuilder bb = new ButtonBarBuilder();
            bb.addGlue();
            bb.addButton(ok);
            bb.addButton(cancel);
            bb.addGlue();
            getContentPane().add(main, BorderLayout.CENTER);
            getContentPane().add(bb.getPanel(), BorderLayout.SOUTH);

            pack();
        }

        public void setVisible(boolean visible) {
            if (visible)
                canceled = true;

            String[] dirs = metaData.getFileDirectory(GUIGlobals.FILE_FIELD);
            if (dirs.length == 0) {

                autoSetNone.setSelected(true);
                autoSetNone.setEnabled(false);
                autoSetAll.setEnabled(false);
                autoSetUnset.setEnabled(false);
            } else {
                autoSetNone.setEnabled(true);
                autoSetAll.setEnabled(true);
                autoSetUnset.setEnabled(true);
            }

            new FocusRequester(ok);
            super.setVisible(visible);

        }

        public boolean canceled() {
            return canceled;
        }
    }
}
