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

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

import javax.swing.JMenuItem;
import javax.swing.JOptionPane;

import net.sf.jabref.*;
import net.sf.jabref.gui.ImportInspectionDialog;
import net.sf.jabref.gui.FileDialogs;
import net.sf.jabref.labelPattern.LabelPatternUtil;
import net.sf.jabref.undo.NamedCompound;
import net.sf.jabref.undo.UndoableInsertEntry;
import net.sf.jabref.undo.UndoableRemoveEntry;
import net.sf.jabref.util.Pair;

/* 
 * TODO: could separate the "menu item" functionality from the importing functionality
 * 
 */
public class ImportMenuItem extends JMenuItem implements ActionListener {

    JabRefFrame frame;
    boolean openInNew;
    MyWorker worker = null;
    ImportFormat importer;
    IOException importError = null;

    public ImportMenuItem(JabRefFrame frame, boolean openInNew) {
        this(frame, openInNew, null);
    }

    public ImportMenuItem(JabRefFrame frame, boolean openInNew, ImportFormat importer) {
        super(importer != null ? importer.getFormatName()
                : Globals.lang("Autodetect format"));
        this.importer = importer;
        this.frame = frame;
        this.openInNew = openInNew;
        addActionListener(this);
    }


    public void actionPerformed(ActionEvent e) {
        worker = new MyWorker();
        worker.init();
        worker.getWorker().run();
        worker.getCallBack().update();
    }
    
    /**
     * Automatically imports the files given as arguments
     * @param filenames List of files to import
     */
    public void automatedImport(String filenames[]) {
        // replace the work of the init step:
        MyWorker worker = new MyWorker();
        worker.fileOk = true;
        worker.filenames = filenames;
        
        worker.getWorker().run();
        worker.getCallBack().update();
    }


    class MyWorker extends AbstractWorker {
        String[] filenames = null, formatName = null;
        ParserResult bibtexResult = null; // Contains the merged import results
        boolean fileOk = false;

        public void init() {
            importError = null;
            filenames = FileDialogs.getMultipleFiles(frame,
                    new File(Globals.prefs.get("workingDirectory")),
                    (importer != null ? importer.getExtensions() : null), true);

            if ((filenames != null) && (filenames.length > 0)) {
                frame.block();
                frame.output(Globals.lang("Starting import"));
                fileOk = true;
                
                Globals.prefs.put("workingDirectory", filenames[0]);
            }
        }

        public void run() {
            if (!fileOk)
                return;

            // We import all files and collect their results:
			List<Pair<String, ParserResult>> imports = new ArrayList<Pair<String, ParserResult>>();
			for (String filename : filenames) {
				try {
					if (importer != null) {
						// Specific importer:
						ParserResult pr = new ParserResult(
							Globals.importFormatReader.importFromFile(importer,
								filename, frame));

						imports.add(new Pair<String, ParserResult>(importer
							.getFormatName(), pr));
					} else {
						// Unknown format:
                        frame.output(Globals.lang("Importing in unknown format")+"...");
                        // This import method never throws an IOException:
                        imports.add(Globals.importFormatReader
							.importUnknownFormat(filename));
					}
				} catch (IOException e) {
					// This indicates that a specific importer was specified, and that
                    // this importer has thrown an IOException. We store the exception,
                    // so a relevant error message can be displayed.
                    importError = e;
                }
			}



            // Ok, done. Then try to gather in all we have found. Since we might
			// have found
            // one or more bibtex results, it's best to gather them in a
			// BibtexDatabase.
            bibtexResult = mergeImportResults(imports);
            
            
            /* show parserwarnings, if any. */
			for (Pair<String, ParserResult> p : imports) {
                if (p != null) {
                    ParserResult pr = p.v;
                    if (pr.hasWarnings()) {
                        if (Globals.prefs
                                .getBoolean("displayKeyWarningDialogAtStartup")
                                && pr.hasWarnings()) {
                            String[] wrns = pr.warnings();
                            StringBuffer wrn = new StringBuffer();
                            for (int j = 0; j < wrns.length; j++)
                                wrn.append(j + 1).append(". ").append(wrns[j])
                                        .append("\n");
                            if (wrn.length() > 0)
                                wrn.deleteCharAt(wrn.length() - 1);
                            JOptionPane.showMessageDialog(frame, wrn.toString(),
                                    Globals.lang("Warnings"),
                                    JOptionPane.WARNING_MESSAGE);
                        }
                    }
                }
			}
        }

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

            // TODO: undo is not handled properly here, except for the entries
			// added by
            //  the import inspection dialog.
            if (bibtexResult != null) {
                if (!openInNew) {
                    final BasePanel panel = (BasePanel) frame.getTabbedPane().getSelectedComponent();
                    BibtexDatabase toAddTo = panel.database();
                    
                    // Use the import inspection dialog if it is enabled in preferences, and
                    // (there are more than one entry or the inspection dialog is also enabled
                    // for single entries):
                    if (Globals.prefs.getBoolean("useImportInspectionDialog") &&
                            (Globals.prefs.getBoolean("useImportInspectionDialogForSingle")
                                    || (bibtexResult.getDatabase().getEntryCount() > 1))) {
                        ImportInspectionDialog diag = new ImportInspectionDialog(frame, panel,
                                BibtexFields.DEFAULT_INSPECTION_FIELDS,
                                Globals.lang("Import"), openInNew);
                        diag.addEntries(bibtexResult.getDatabase().getEntries());
                        diag.entryListComplete();
                        Util.placeDialog(diag, frame);
                        diag.setVisible(true);
                        diag.toFront();
                    } else {
                        boolean generateKeys = Globals.prefs.getBoolean("generateKeysAfterInspection");
                        NamedCompound ce = new NamedCompound(Globals.lang("Import entries"));

                        // Check if we should unmark entries before adding the new ones:
                        if (Globals.prefs.getBoolean("unmarkAllEntriesBeforeImporting"))
                            for (BibtexEntry entry : toAddTo.getEntries()) {
                                Util.unmarkEntry(entry, true, toAddTo, ce);
                            }


                        for (BibtexEntry entry : bibtexResult.getDatabase().getEntries()){
                            try {
                                // Check if the entry is a duplicate of an existing one:
                                boolean keepEntry = true;
                                BibtexEntry duplicate = DuplicateCheck.containsDuplicate(toAddTo, entry);
                                if (duplicate != null) {
                                    int answer = DuplicateResolverDialog.resolveDuplicateInImport
                                            (frame, duplicate, entry);
                                    // The upper entry is the
                                    if (answer == DuplicateResolverDialog.DO_NOT_IMPORT)
                                        keepEntry = false;
                                    if (answer == DuplicateResolverDialog.IMPORT_AND_DELETE_OLD) {
                                        // Remove the old one and import the new one.
                                        toAddTo.removeEntry(duplicate.getId());
                                        ce.addEdit(new UndoableRemoveEntry(toAddTo, duplicate, panel));
                                    }
                                }
                                // Add the entry, if we are supposed to:
                                if (keepEntry) {
                                    toAddTo.insertEntry(entry);
                                    // Generate key, if we are supposed to:
                                    if (generateKeys) {
                                        LabelPatternUtil.makeLabel(bibtexResult.getMetaData(), toAddTo, entry);
                                        //System.out.println("gen:"+entry.getCiteKey());
                                    }
                                    ce.addEdit(new UndoableInsertEntry(toAddTo, entry, panel));
                                }
                            } catch (KeyCollisionException e) {
                                e.printStackTrace();
                            }
                        }
                        ce.end();
                        if (ce.hasEdits()) {
                            panel.undoManager.addEdit(ce);
                            panel.markBaseChanged();
                        }

                    }                    

                }

                else {
                    frame.addTab(bibtexResult.getDatabase(), bibtexResult.getFile(),
                            bibtexResult.getMetaData(), Globals.prefs.get("defaultEncoding"), true);
                    frame.output(Globals.lang("Imported entries") + ": " + bibtexResult.getDatabase().getEntryCount());
                }


            } else {
                if (importer == null)
                    frame.output(Globals.lang("Could not find a suitable import format."));
                else {
                    // Import in a specific format was specified. Check if we have stored error information:
                    if (importError != null) {
                        JOptionPane.showMessageDialog(frame, importError.getMessage(), Globals.lang("Import failed"),
                              JOptionPane.ERROR_MESSAGE);
                    }
                    else {
                        JOptionPane.showMessageDialog(frame, Globals.lang("No entries found. Please make sure you are "
                                      +"using the correct import filter."), Globals.lang("Import failed"),
                              JOptionPane.ERROR_MESSAGE);
                    }
                }
            }
            frame.unblock();
        }
    }

    public ParserResult mergeImportResults(List<Pair<String, ParserResult>> imports) {
        BibtexDatabase database = new BibtexDatabase();
        ParserResult directParserResult = null;
        boolean anythingUseful = false;

        for (Pair<String, ParserResult> importResult : imports){
            if (importResult == null)
                continue;
            if (importResult.p.equals(ImportFormatReader.BIBTEX_FORMAT)){
        	    // Bibtex result. We must merge it into our main base.
                ParserResult pr = importResult.v;

                anythingUseful = anythingUseful
                        || ((pr.getDatabase().getEntryCount() > 0) || (pr.getDatabase().getStringCount() > 0));
                
                // Record the parserResult, as long as this is the first bibtex result:
                if (directParserResult == null) {
                    directParserResult = pr;
                }

                // Merge entries:
                for (BibtexEntry entry : pr.getDatabase().getEntries()) {
                    database.insertEntry(entry);
                }
                
                // Merge strings:
                for (BibtexString bs : pr.getDatabase().getStringValues()){
                    try {
                        database.addString((BibtexString)bs.clone());
                    } catch (KeyCollisionException e) {
                        // TODO: This means a duplicate string name exists, so it's not
                        // a very exceptional situation. We should maybe give a warning...?
                    }
                }
            } else {
            	
            	ParserResult pr = importResult.v;
				Collection<BibtexEntry> entries = pr.getDatabase().getEntries();

				anythingUseful = anythingUseful | (entries.size() > 0);

				// set timestamp and owner
				Util.setAutomaticFields(entries, Globals.prefs.getBoolean("overwriteOwner"),
                        Globals.prefs.getBoolean("overwriteTimeStamp"),
                        !openInNew && Globals.prefs.getBoolean("markImportedEntries")); // set timestamp and owner

                for (BibtexEntry entry : entries){
					database.insertEntry(entry);
				}
			}
        }

        if (!anythingUseful)
            return null;

        if ((imports.size() == 1) && (directParserResult != null)) {
            return directParserResult;
        } else {

            ParserResult pr = new ParserResult(database, new MetaData(), new HashMap<String, BibtexEntryType>());
            return pr;

        }
    }

}
