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

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.URL;
import java.util.*;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.UndoManager;

import net.sf.jabref.*;
import net.sf.jabref.undo.UndoableFieldChange;
import net.sf.jabref.util.CaseChanger;

/**
 * Created by IntelliJ IDEA.
 * User: alver
 * Date: Sep 16, 2005
 * Time: 10:49:08 PM
 * To browseOld this template use File | Settings | File Templates.
 */
public class JournalAbbreviations {

    static String TOOLTIPTEXT = "<HTML>"+Globals.lang("Switches between full and abbreviated journal name "
        +"if the journal name is known.")
        +"<BR>"+Globals.lang("To set up, go to <B>Tools -> Manage journal abbreviations</B>")+".</HTML>";
    TreeMap<String, String> fullNameKeyed = new TreeMap<String, String>();
    HashMap<String, String> abbrNameKeyed = new HashMap<String, String>();
    HashMap<String, String> abbrNoDotsToAbbr = new HashMap<String, String>();
    TreeMap<String, String> all = new TreeMap<String, String>();

    public JournalAbbreviations() {
        
    }

    public JournalAbbreviations(String resource) {
        readJournalList(resource);
    }

    public JournalAbbreviations(File file) throws FileNotFoundException {
        readJournalList(file);
    }

    /**
     * Get an iterator for the known journals in alphabetical order.
     * @return Iterator for journal full names
     */
    public Iterator<String> fullNameIterator() {
        return fullNameKeyed.keySet().iterator();
    }

    public boolean isKnownName(String journalName) {
        String s = journalName.toLowerCase();
        return ((fullNameKeyed.get(s) != null) || (abbrNameKeyed.get(s) != null) || (abbrNoDotsToAbbr.get(s) != null));
    }

    public boolean isAbbreviatedName(String journalName) {
        String s = journalName.toLowerCase();
        return ((abbrNameKeyed.get(s) != null) || (abbrNoDotsToAbbr.get(s) != null));
    }

    public String dotsToNodots(String name) {
        return name.replaceAll("\\.", " ").replaceAll("  ", " ").trim();
    }

    /**
     * Attempts to get the abbreviated name of the journal given. Returns null if no
     * abbreviated name is known.
     * @param journalName The journal name to abbreviate.
     * @param withDots True if the abbreviation should have dots.
     * if only the first character should be.
     * @return The abbreviated name, or null if it couldn't be found.
     */
    public String getAbbreviatedName(String journalName, boolean withDots) {
        String s = journalName.toLowerCase();
        String abbr;
        if (fullNameKeyed.containsKey(s)) {
            abbr = fullNameKeyed.get(s);
        }
        else if (abbrNameKeyed.containsKey(s)) {
            abbr = journalName;
        }
        else if (abbrNoDotsToAbbr.containsKey(s)) {
            abbr = abbrNoDotsToAbbr.get(s);
        } else
            return null;

        if (!withDots) {
            abbr = dotsToNodots(abbr);
        }

        return abbr;
    }

    /**
     * Attempts to get the full name of the abbreviation given. Returns null if no
     * full name is known.
     * @param journalName The abbreviation to resolve.
     * @return The full name, or null if it couldn't be found.
     */
    public String getFullName(String journalName) {
        // Normalize name first:
        String s = getAbbreviatedName(journalName, true);
	if (s != null) 
	    s = s.toLowerCase();
	else
	    return null;
        Object o = abbrNameKeyed.get(s);
        if (o == null) {
            if (fullNameKeyed.containsKey(s))
                o = s;
            else
                return null;
        }
        s = (String)o;
        return s;//caseChanger.changeCase(s, CaseChanger.UPPER_EACH_FIRST);
    }

    public void readJournalList(String resourceFileName) {
        URL url = JournalAbbreviations.class.getResource(resourceFileName);
        try {
            readJournalList(new InputStreamReader(url.openStream()));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void readJournalList(File file) throws FileNotFoundException {
        readJournalList(new FileReader(file));
    }

    /**
     * Read the given file, which should contain a list of journal names and their
     * abbreviations. Each line should be formatted as: "Full Journal Name=Abbr. Journal Name"
     * @param in
     */
    public void readJournalList(Reader in) throws FileNotFoundException {
        BufferedReader reader = new BufferedReader(in);
        try {
            String line;
            while ((line = reader.readLine()) != null) {
                //System.out.println(line);
                if (line.startsWith("#"))
                    continue;
                String[] parts = line.split("=");
                if (parts.length == 2) {
                    String fullName = parts[0].trim();
                    String fullNameLC = fullName.toLowerCase();
                    String abbrName = parts[1].trim();
                    if (abbrName.indexOf(';') >= 0) {
                        String[] restParts = abbrName.split(";");
                        abbrName = restParts[0];
                    }
                    String abbrNameLC = abbrName.toLowerCase();
                    String abbrNoDots = dotsToNodots(abbrName);
                    String abbrNoDotsLC = abbrNoDots.toLowerCase();
                    //System.out.println(abbrNoDots);
                    if ((fullName.length()>0) && (abbrName.length()>0)) {
                        //System.out.println("'"+fullName+"' : '"+abbrNoDots+"'");
                        fullNameKeyed.put(fullNameLC, abbrName);
                        abbrNameKeyed.put(abbrNameLC, fullName);
                        abbrNoDotsToAbbr.put(abbrNoDotsLC, abbrName);
                        all.put(fullName, abbrName);
                    }
                }
            }

        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            try {
                reader.close();
            } catch (IOException ex2) {
                ex2.printStackTrace();
            }
        }
    }

    /**
     * Abbreviate the journal name of the given entry.
     * @param database The database the entry belongs to, or null if no database.
     * @param entry The entry to be treated.
     * @param fieldName The field name (e.g. "journal")
     * @param ce If the entry is changed, add an edit to this compound.
     * @param withDots True if the abbreviations should have dots.
     * @return true if the entry was changed, false otherwise.
     */
    public boolean abbreviate(BibtexDatabase database, BibtexEntry entry,
                              String fieldName, CompoundEdit ce, boolean withDots) {
        String text = entry.getField(fieldName);
        if (text == null)
            return false;
        String origText = text;
        if (database != null)
            text = database.resolveForStrings(text);
        if (isKnownName(text) && !isAbbreviatedName(text)) {
            String newText = getAbbreviatedName(text, withDots);
            if (newText == null)
                return false;
            entry.setField(fieldName, newText);
            ce.addEdit(new UndoableFieldChange(entry, fieldName, origText, newText));
            return true;
        } else {
            String unabbr = getFullName(text);
            if (unabbr != null) {
                String newText = getAbbreviatedName(unabbr, withDots);
                if (newText == null)
                    return false;
                entry.setField(fieldName, newText);
                ce.addEdit(new UndoableFieldChange(entry, fieldName, origText, newText));
                return true;
            } else
                return false;
        }
    }

    /**
     * Unabbreviate the journal name of the given entry.
     * @param entry The entry to be treated.
     * @param fieldName The field name (e.g. "journal")
     * @param ce If the entry is changed, add an edit to this compound.
     * @return true if the entry was changed, false otherwise.
     */
    public boolean unabbreviate(BibtexDatabase database, BibtexEntry entry,
                                String fieldName, CompoundEdit ce) {
        String text = entry.getField(fieldName);
        if (text == null)
            return false;
        String origText = text;
        if (database != null)
            text = database.resolveForStrings(text);
        if (isKnownName(text) && isAbbreviatedName(text)) {
            String newText = getFullName(text);
            if (newText == null)
                return false;
            entry.setField(fieldName, newText);
            ce.addEdit(new UndoableFieldChange(entry, fieldName, origText, newText));
            return true;
        } else
            return false;
    }


    public Map<String, String> getJournals() {
        return Collections.unmodifiableMap(all);
    }

    /**
     * Create a control panel for the entry editor's journal field, to toggle
     * abbreviated/full journal name
     * @param editor The FieldEditor for the journal field.
     * @return The control panel for the entry editor.
     */
    public static JComponent getNameSwitcher(final EntryEditor entryEditor, final FieldEditor editor,
                                      final UndoManager undoManager) {
        JButton button = new JButton(Globals.lang("Toggle abbreviation"));
        button.setToolTipText(TOOLTIPTEXT);
        button.addActionListener(new ActionListener() {
            boolean withDots = true;
            public void actionPerformed(ActionEvent actionEvent) {
                String text = editor.getText();
                if (Globals.journalAbbrev.isKnownName(text)) {
                    String s;
                    if (Globals.journalAbbrev.isAbbreviatedName(text)) {
                        //System.out.println(withDots);
                        if (!withDots) {
                            s = Globals.journalAbbrev.getAbbreviatedName(text, false);
                            withDots = true;
                        } else {
                            s = Globals.journalAbbrev.getFullName(text);
                        }
                    }
                    else {
                        s = Globals.journalAbbrev.getAbbreviatedName(text, true);
                        withDots = false;
                    }

                    if (s != null) {
                        editor.setText(s);
                        entryEditor.storeFieldAction.actionPerformed(new ActionEvent(editor, 0, ""));
                        undoManager.addEdit(new UndoableFieldChange(entryEditor.getEntry(), editor.getFieldName(),
                                text, s));
                    }
                }
            }
        });

        return button;
    }

    public TableModel getTableModel() {
        Object[][] cells = new Object[fullNameKeyed.size()][2];
        int row = 0;
        for (Iterator<String> i=fullNameIterator(); i.hasNext();) {
            String name = i.next();
            cells[row][0] = getFullName(name);
            cells[row][1] = getAbbreviatedName(name, true);
            row++;
        }
        DefaultTableModel tableModel = new DefaultTableModel(cells, new Object[] {Globals.lang("Full name"),
            Globals.lang("Abbreviation")}) {

            public boolean isCellEditable(int row, int column) {
                return false;
            }
        };

        return tableModel;
    }

}
