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

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.TreeSet;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.DefaultCellEditor;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.LayoutFocusTraversalPolicy;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumnModel;
import javax.swing.undo.CompoundEdit;

import net.sf.jabref.export.LatexFieldFormatter;
import net.sf.jabref.help.HelpAction;
import net.sf.jabref.undo.UndoableInsertString;
import net.sf.jabref.undo.UndoableRemoveString;
import net.sf.jabref.undo.UndoableStringChange;

public class StringDialog extends JDialog {

    // A reference to the entry this object works on.
    BibtexDatabase base;
    JabRefFrame frame;
    BasePanel panel;
    JabRefPreferences prefs;
    TreeSet<BibtexString> stringsSet; // Our locally sorted set of strings.
    Object[] strings;

    // Layout objects.
    GridBagLayout gbl = new GridBagLayout();
    GridBagConstraints con = new GridBagConstraints();
    JLabel lab;
    Container conPane = getContentPane();
    JToolBar tlb = new JToolBar();
    JPanel pan = new JPanel();
    StringTable table;
    HelpAction helpAction;

    public StringDialog(JabRefFrame frame, BasePanel panel,
			BibtexDatabase base, JabRefPreferences prefs) {
	super(frame);
	this.frame = frame;
	this.panel = panel;
	this.base = base;
	this.prefs = prefs;

	sortStrings();

	helpAction = new HelpAction
	    (frame.helpDiag, GUIGlobals.stringEditorHelp, "Help");


	addWindowListener(new WindowAdapter() {
		public void windowClosing(WindowEvent e) {
		    closeAction.actionPerformed(null);
		}
	    });

	// We replace the default FocusTraversalPolicy with a subclass
	// that only allows the StringTable to gain keyboard focus.
	setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
		protected boolean accept(Component c) {
		    return (super.accept(c) && (c instanceof StringTable));
		}
	    });

	setLocation(prefs.getInt("stringsPosX"), prefs.getInt("stringsPosY"));
	setSize(prefs.getInt("stringsSizeX"), prefs.getInt("stringsSizeY"));

	pan.setLayout(gbl);
	con.fill = GridBagConstraints.BOTH;
	con.weighty = 1;
	con.weightx = 1;

	StringTableModel stm = new StringTableModel(this, base);
	table = new StringTable(stm);
	if (base.getStringCount() > 0)
	    table.setRowSelectionInterval(0,0);

	gbl.setConstraints(table.getPane(), con);
	pan.add(table.getPane());

	InputMap im = tlb.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
	ActionMap am = tlb.getActionMap();
	im.put(prefs.getKey("String dialog, add string"), "add");
	am.put("add", newStringAction);
	im.put(prefs.getKey("String dialog, remove string"), "remove");
	am.put("remove", removeStringAction);
	//im.put(prefs.getKey("String dialog, move string up"), "up");
	//am.put("up", stringUpAction);
	//im.put(prefs.getKey("String dialog, move string down"), "down");
	//am.put("down", stringDownAction);
	im.put(prefs.getKey("Close dialog"), "close");
	am.put("close", closeAction);
	im.put(prefs.getKey("Help"), "help");
	am.put("help", helpAction);
	im.put(prefs.getKey("Undo"), "undo");
	am.put("undo", undoAction);
	im.put(prefs.getKey("Redo"), "redo");
	am.put("redo", redoAction);

	//tlb.add(closeAction);
	//tlb.addSeparator();
	tlb.add(newStringAction);
	tlb.add(removeStringAction);
	tlb.addSeparator();
	//tlb.add(stringUpAction);
	//tlb.add(stringDownAction);
	tlb.addSeparator();
	tlb.add(helpAction);
	conPane.add(tlb, BorderLayout.NORTH);
	conPane.add(pan, BorderLayout.CENTER);

	if (panel.getFile() != null)
	    setTitle(Globals.lang(GUIGlobals.stringsTitle)+": "+panel.getFile().getName());
	else
	    setTitle(Globals.lang(GUIGlobals.stringsTitle)+": "+Globals.lang(GUIGlobals.untitledTitle));

    }

    class StringTable extends JTable {
	JScrollPane sp = new JScrollPane(this);
	public StringTable(StringTableModel stm) {
	    super(stm);
	    setShowVerticalLines(true);
	    setShowHorizontalLines(true);
	    setColumnSelectionAllowed(true);
	    DefaultCellEditor dce = new DefaultCellEditor(new JTextField());
	    dce.setClickCountToStart(2);
	    setDefaultEditor(String.class, dce);
	    TableColumnModel cm = getColumnModel();
	    cm.getColumn(0).setPreferredWidth(800);
	    cm.getColumn(1).setPreferredWidth(2000);
	    sp.getViewport().setBackground(Globals.prefs.getColor("tableBackground"));
	    // getInputMap().remove(GUIGlobals.exitDialog);
	    getInputMap().put(frame.prefs.getKey("Close dialog"), "close");
	    getActionMap().put("close", closeAction);
	    getInputMap().put(frame.prefs.getKey("Help"), "help");
	    getActionMap().put("help", helpAction);

	}

	public JComponent getPane() {
	    return sp;
	}

    }

    private void sortStrings() {
	// Rebuild our sorted set of strings:
	stringsSet = new TreeSet<BibtexString>(new BibtexStringComparator(false));
	for (String s : base.getStringKeySet()){
	    stringsSet.add(base.getString(s));
	}
	strings = stringsSet.toArray();
    }

    public void refreshTable() {
	sortStrings();
	table.revalidate();
	table.clearSelection();
	table.repaint();
    }

    class StringTableModel extends AbstractTableModel {

	BibtexDatabase base;
	StringDialog parent;

	public StringTableModel(StringDialog parent, BibtexDatabase base) {
	    this.parent = parent;
	    this.base = base;
	}

	public Object getValueAt(int row, int col) {
	    return ((col == 0) ?
		    ((BibtexString)strings[row]).getName() :
		    ((BibtexString)strings[row]).getContent());
	}

	public void setValueAt(Object value, int row, int col) {
	    //	    if (row >= base.getStringCount())
	    //	return; // After a Remove operation the program somehow
	                // thinks the user is still editing an entry,
	                // which might now be outside
	    if (col == 0) {
		// Change name of string.
		if (!((String)value).equals(((BibtexString)strings[row]).getName())) {
		    if (base.hasStringLabel((String)value))
			JOptionPane.showMessageDialog(parent,
						      Globals.lang("A string with that label "
								   +"already exists"),
						      Globals.lang("Label"),
						      JOptionPane.ERROR_MESSAGE);
                      else if (((String)value).indexOf(" ") >= 0) {
                        JOptionPane.showMessageDialog
                            (parent,
                             Globals.lang("The label of the string can not contain spaces."),
                             Globals.lang("Label"),
                             JOptionPane.ERROR_MESSAGE);
                      }
                      else if (((String)value).indexOf("#") >= 0) {
                        JOptionPane.showMessageDialog
                            (parent,
                            Globals.lang("The label of the string can not contain the '#' character."),
                            Globals.lang("Label"),
                            JOptionPane.ERROR_MESSAGE);
                      }   
                      else if (isNumber((String)value)) {
                          JOptionPane.showMessageDialog
                              (parent,
                               Globals.lang("The label of the string can not be a number."),
                               Globals.lang("Label"),
                               JOptionPane.ERROR_MESSAGE);
		    }
		    else {
			// Store undo information.
			BibtexString subject = (BibtexString)strings[row];
			panel.undoManager.addEdit
			    (new UndoableStringChange
			     (panel, subject, true,
			      subject.getName(), (String)value));
			subject.setName((String)value);
			panel.markBaseChanged();
			refreshTable();
		    }
		}
	    } else {
		// Change content of string.
		BibtexString subject = (BibtexString)strings[row];

		if (!((String)value).equals(subject.getContent())) {
                    try {
                        (new LatexFieldFormatter()).format((String)value, "__dummy");
                    } catch (IllegalArgumentException ex) {
                        return;
                    }
		    // Store undo information.
		    panel.undoManager.addEdit
			(new UndoableStringChange
			 (panel, subject, false,
			  subject.getContent(), (String)value));

		    subject.setContent((String)value);
		    panel.markBaseChanged();
		}
	    }
	}

	public int getColumnCount() {
	    return 2;
	}

	public int getRowCount() {
	    return strings.length; //base.getStringCount();
	}

	public String getColumnName(int col) {
	    return ((col == 0) ?
		    Globals.lang("Name") : Globals.lang("Content"));
	}

	public boolean isCellEditable(int row, int col) {
	    return true;
	}
    }

    protected boolean isNumber(String name) {
	// A pure integer number can not be used as a string label,
	// since Bibtex will read it as a number.
	try {
	    Integer.parseInt(name);
	    return true;
	} catch (NumberFormatException ex) {
	    return false;
	}

    }

    protected void assureNotEditing() {
	if (table.isEditing()) {
	    int col = table.getEditingColumn(),
		row = table.getEditingRow();
	    table.getCellEditor(row, col).stopCellEditing();
	}
    }

    // The action concerned with closing the window.
    CloseAction closeAction = new CloseAction(this);
    class CloseAction extends AbstractAction {
	StringDialog parent;
	public CloseAction(StringDialog parent) {
	    super("Close window");
	    //, new ImageIcon(GUIGlobals.closeIconFile));
	    putValue(SHORT_DESCRIPTION, Globals.lang("Close dialog"));
	    this.parent = parent;
	}
	public void actionPerformed(ActionEvent e) {
	    panel.stringsClosing();
	    dispose();
	    Point p = getLocation();
	    Dimension d = getSize();
	    prefs.putInt("stringsPosX", p.x);
	    prefs.putInt("stringsPosY", p.y);
	    prefs.putInt("stringsSizeX", d.width);
	    prefs.putInt("stringsSizeY", d.height);
	}
    }

    NewStringAction newStringAction = new NewStringAction(this);
    class NewStringAction extends AbstractAction {
	StringDialog parent;
	public NewStringAction(StringDialog parent) {
	    super("New string",
		  GUIGlobals.getImage("add"));
	    putValue(SHORT_DESCRIPTION, Globals.lang("New string"));
	    this.parent = parent;
	}
	public void actionPerformed(ActionEvent e) {
	    String name =
		JOptionPane.showInputDialog(parent,
					    Globals.lang("Please enter the string's label"));
	    if (name == null)
		return;
	    if (isNumber(name)) {
		JOptionPane.showMessageDialog
		    (parent,
		     Globals.lang("The label of the string can not be a number."),
		     Globals.lang("Label"),
		     JOptionPane.ERROR_MESSAGE);
		return;
	    }
            if (name.indexOf("#") >= 0) {
             JOptionPane.showMessageDialog
                 (parent,
                  Globals.lang("The label of the string can not contain the '#' character."),
                  Globals.lang("Label"),
                  JOptionPane.ERROR_MESSAGE);
             return;
           }           
           if (name.indexOf(" ") >= 0) {
             JOptionPane.showMessageDialog
                 (parent,
                  Globals.lang("The label of the string can not contain spaces."),
                  Globals.lang("Label"),
                  JOptionPane.ERROR_MESSAGE);
             return;
           }
	    try {
		String newId = Util.createNeutralId();
		BibtexString bs = new BibtexString(newId, name, "");

		// Store undo information:
		panel.undoManager.addEdit
		    (new UndoableInsertString
		     (panel, panel.database, bs));

		base.addString(bs);
		refreshTable();
		//		table.revalidate();
		panel.markBaseChanged();
	    } catch (KeyCollisionException ex) {
		JOptionPane.showMessageDialog(parent,
					      Globals.lang("A string with that label "
							   +"already exists"),
					      Globals.lang("Label"),
					      JOptionPane.ERROR_MESSAGE);
	    }
	}
    }

    StoreContentAction storeContentAction = new StoreContentAction(this);
    class StoreContentAction extends AbstractAction {
	StringDialog parent;
	public StoreContentAction(StringDialog parent) {
	    super("Store string",
		  GUIGlobals.getImage("add"));
	    putValue(SHORT_DESCRIPTION, Globals.lang("Store string"));
	    this.parent = parent;
	}
	public void actionPerformed(ActionEvent e) {
	}
    }

    RemoveStringAction removeStringAction = new RemoveStringAction(this);
    class RemoveStringAction extends AbstractAction {
	StringDialog parent;
	public RemoveStringAction(StringDialog parent) {
	    super("Remove selected strings",
		  GUIGlobals.getImage("remove"));
	    putValue(SHORT_DESCRIPTION, Globals.lang("Remove selected strings"));
	    this.parent = parent;
	}
	public void actionPerformed(ActionEvent e) {
	    int[] sel = table.getSelectedRows();
	    if (sel.length > 0) {

		// Make sure no cell is being edited, as caused by the
		// keystroke. This makes the content hang on the screen.
		assureNotEditing();

		String msg = Globals.lang("Really delete the selected")+" "+
		    ((sel.length>1) ? sel.length+" "+Globals.lang("entries")
		     : Globals.lang("entry"))+"?";
		int answer = JOptionPane.showConfirmDialog(parent, msg, Globals.lang("Delete strings"),
							   JOptionPane.YES_NO_OPTION,
							   JOptionPane.QUESTION_MESSAGE);
		if (answer == JOptionPane.YES_OPTION) {
		    CompoundEdit ce = new CompoundEdit();
		    for (int i=sel.length-1; i>=0; i--) {
			// Delete the strings backwards to avoid moving indexes.

			BibtexString subject = (BibtexString)strings[sel[i]];

			// Store undo information:
			ce.addEdit(new UndoableRemoveString
				   (panel, base,
				    subject));

			base.removeString(subject.getId());
		    }
		    ce.end();
		    panel.undoManager.addEdit(ce);

		    //table.revalidate();
		    refreshTable();
		    if (base.getStringCount() > 0)
			table.setRowSelectionInterval(0,0);
		    //table.repaint();
		    //panel.markBaseChanged();
		}
	    }
	}
    }

    /*    StringUpAction stringUpAction = new StringUpAction();
    class StringUpAction extends AbstractAction {
	public StringUpAction() {
	    super("Move string up",
		  new ImageIcon(GUIGlobals.upIconFile));
	    putValue(SHORT_DESCRIPTION, Globals.lang("Move string up"));
	}
	public void actionPerformed(ActionEvent e) {
	    int[] sel = table.getSelectedRows();
	    if ((sel.length == 1) && (sel[0] > 0)) {

		// Make sure no cell is being edited, as caused by the
		// keystroke. This makes the content hang on the screen.
		assureNotEditing();
		// Store undo information:
		panel.undoManager.addEdit(new UndoableMoveString
					      (panel, base, sel[0], true));

		BibtexString bs = base.getString(sel[0]);
		base.removeString(sel[0]);
		try {
		    base.addString(bs, sel[0]-1);
		} catch (KeyCollisionException ex) {}
		table.revalidate();
		table.setRowSelectionInterval(sel[0]-1, sel[0]-1);
		table.repaint();
		panel.markBaseChanged();
	    }
	}
    }

    StringDownAction stringDownAction = new StringDownAction();
    class StringDownAction extends AbstractAction {
	public StringDownAction() {
	    super("Move string down",
		  new ImageIcon(GUIGlobals.downIconFile));
	    putValue(SHORT_DESCRIPTION, Globals.lang("Move string down"));
	}
	public void actionPerformed(ActionEvent e) {
	    int[] sel = table.getSelectedRows();
	    if ((sel.length == 1) && (sel[0]+1 < base.getStringCount())) {

		// Make sure no cell is being edited, as caused by the
		// keystroke. This makes the content hang on the screen.
		assureNotEditing();


		// Store undo information:
		panel.undoManager.addEdit(new UndoableMoveString
					      (panel, base, sel[0], false));


		BibtexString bs = base.getString(sel[0]);
		base.removeString(sel[0]);
		try {
		    base.addString(bs, sel[0]+1);
		} catch (KeyCollisionException ex) {}
		table.revalidate();
		table.setRowSelectionInterval(sel[0]+1, sel[0]+1);
		table.repaint();
		panel.markBaseChanged();
	    }

	}
    }*/

    UndoAction undoAction = new UndoAction();
    class UndoAction extends AbstractAction {
	public UndoAction() {
	    super("Undo", GUIGlobals.getImage("undo"));
	    putValue(SHORT_DESCRIPTION, Globals.lang("Undo"));
	}
	public void actionPerformed(ActionEvent e) {
	    try {
		panel.runCommand("undo");
	    } catch (Throwable ex) {}
	}
    }

    RedoAction redoAction = new RedoAction();
    class RedoAction extends AbstractAction {
	public RedoAction() {
	    super("Undo", GUIGlobals.getImage("redo"));
	    putValue(SHORT_DESCRIPTION, Globals.lang("Redo"));
	}
	public void actionPerformed(ActionEvent e) {
	    try {
		panel.runCommand("redo");
	    } catch (Throwable ex) {}
	}
    }


}
