package net.sf.jabref.gui.mergeentries;

import java.awt.event.ActionEvent;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JSeparator;

import net.sf.jabref.gui.BasePanel;
import net.sf.jabref.gui.undo.NamedCompound;
import net.sf.jabref.gui.undo.UndoableChangeType;
import net.sf.jabref.gui.undo.UndoableFieldChange;
import net.sf.jabref.gui.util.WindowLocation;
import net.sf.jabref.logic.l10n.Localization;
import net.sf.jabref.model.entry.BibEntry;
import net.sf.jabref.model.entry.InternalBibtexFields;
import net.sf.jabref.preferences.JabRefPreferences;

import com.jgoodies.forms.builder.ButtonBarBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.ColumnSpec;
import com.jgoodies.forms.layout.FormLayout;
import com.jgoodies.forms.layout.RowSpec;

/**
 * Dialog for merging Bibtex entry with fetched data
 */
public class MergeFetchedEntryDialog extends JDialog {

    private final BasePanel panel;
    private final CellConstraints cc = new CellConstraints();
    private final BibEntry originalEntry;
    private final BibEntry fetchedEntry;
    private NamedCompound ce;
    private MergeEntries mergeEntries;
    private final String type;
    private static final String MARGIN = "5px";


    public MergeFetchedEntryDialog(BasePanel panel, BibEntry originalEntry, BibEntry fetchedEntry, String type) {
        super(panel.frame(), Localization.lang("Merge entry with %0 information", type), true);

        this.panel = panel;
        this.originalEntry = originalEntry;
        this.fetchedEntry = fetchedEntry;
        this.type = type;

        // Start setting up the dialog
        init();
    }

    /**
     * Sets up the dialog
     */
    private void init() {
        mergeEntries = new MergeEntries(this.originalEntry, this.fetchedEntry, Localization.lang("Original entry"),
                Localization.lang("Entry from %0", type), panel.getBibDatabaseContext().getMode());

        // Create undo-compound
        ce = new NamedCompound(Localization.lang("Merge entry with %0 information", type));

        FormLayout layout = new FormLayout("fill:700px:grow", "fill:400px:grow, 4px, p, 5px, p");
        this.setLayout(layout);

        this.add(mergeEntries.getMergeEntryPanel(), cc.xy(1, 1));
        this.add(new JSeparator(), cc.xy(1, 3));

        // Create buttons
        ButtonBarBuilder bb = new ButtonBarBuilder();
        bb.addGlue();

        JButton cancel = new JButton(new CancelAction());
        JButton replaceEntry = new JButton(new ReplaceAction());

        bb.addButton(replaceEntry, cancel);
        this.add(bb.getPanel(), cc.xy(1, 5));

        // Add some margin around the layout
        layout.appendRow(RowSpec.decode(MARGIN));
        layout.appendColumn(ColumnSpec.decode(MARGIN));
        layout.insertRow(1, RowSpec.decode(MARGIN));
        layout.insertColumn(1, ColumnSpec.decode(MARGIN));

        WindowLocation pw = new WindowLocation(this, JabRefPreferences.MERGEENTRIES_POS_X,
                JabRefPreferences.MERGEENTRIES_POS_Y, JabRefPreferences.MERGEENTRIES_SIZE_X,
                JabRefPreferences.MERGEENTRIES_SIZE_Y);
        pw.displayWindowAtStoredLocation();

    }

    private class CancelAction extends AbstractAction {
        CancelAction(){
            putValue(Action.NAME, Localization.lang("Cancel"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            panel.output(Localization.lang("Canceled merging entries"));
            dispose();
        }
    }

    private class ReplaceAction extends AbstractAction {
        ReplaceAction(){
            putValue(Action.NAME, Localization.lang("Replace original entry"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            BibEntry mergedEntry = mergeEntries.getMergeEntry();

            // Updated the original entry with the new fields
            Set<String> jointFields = new TreeSet<>(mergedEntry.getFieldNames());
            Set<String> originalFields = new TreeSet<>(originalEntry.getFieldNames());
            boolean edited = false;

            // entry type
            String oldType = originalEntry.getType();
            String newType = mergedEntry.getType();

            if(!oldType.equalsIgnoreCase(newType)) {
                originalEntry.setType(newType);
                ce.addEdit(new UndoableChangeType(originalEntry, oldType, newType));
                edited = true;
            }

            // fields
            for (String field : jointFields) {
                Optional<String> originalString = originalEntry.getField(field);
                Optional<String> mergedString = mergedEntry.getField(field);
                if (!originalString.isPresent() || !originalString.equals(mergedString)) {
                    originalEntry.setField(field, mergedString.get()); // mergedString always present
                    ce.addEdit(new UndoableFieldChange(originalEntry, field, originalString.orElse(null),
                            mergedString.get()));
                    edited = true;
                }
            }

            // Remove fields which are not in the merged entry, unless they are internal fields
            for (String field : originalFields) {
                if (!jointFields.contains(field) && !InternalBibtexFields.isInternalField(field)) {
                    Optional<String> originalString = originalEntry.getField(field);
                    originalEntry.clearField(field);
                    ce.addEdit(new UndoableFieldChange(originalEntry, field, originalString.get(), null)); // originalString always present
                    edited = true;
                }
            }

            if (edited) {
                ce.end();
                panel.getUndoManager().addEdit(ce);
                panel.output(Localization.lang("Updated entry with info from %0", type));
                panel.updateEntryEditorIfShowing();
                panel.markBaseChanged();
            } else {
                panel.output(Localization.lang("No information added"));
            }

            dispose();
        }
    }
}
