/**
 *#########################################################################
 *
 * A component of the Gatherer application, part of the Greenstone digital
 * library suite from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * Author: John Thompson, Greenstone Digital Library, University of Waikato
 *
 * Copyright (C) 1999 New Zealand Digital Library Project
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *########################################################################
 */
package org.greenstone.gatherer.cdm;

import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.Field;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.undo.*;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.DebugStream;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.gui.DesignPaneHeader;
import org.greenstone.gatherer.gui.GLIButton;
import org.greenstone.gatherer.gui.WarningDialog;
import org.greenstone.gatherer.metadata.MetadataElement;
import org.greenstone.gatherer.metadata.MetadataSetManager;
import org.greenstone.gatherer.util.StaticStrings;
import org.greenstone.gatherer.util.Utility;
import org.w3c.dom.*;

import org.fife.ui.rsyntaxtextarea.*;


/** This class maintains a list of format statements, and allows the addition and removal of these statements.
 * @author John Thompson, Greenstone Digital Library, University of Waikato
 * @version 2.3
 */
public class FormatManager
  extends DOMProxyListModel implements SharedByTwoFormatManager {
  
  static final private String BLANK = "blank";
  static final private String FLAG  = "flag";
  static final private String VALUE = "value";
  
  static final private String DATELIST = "DateList";
  static final private String DATELIST_DEFAULT_FORMAT = "<td>[link][icon][/link]</td>\n<td>[highlight]{Or}{[dc.Title],[exp.Title],[ex.Title],Untitled}[/highlight]</td>\n<td>{Or}{[format:dc.Date],[format:exp.Date],[format:ex.Date]}</td>";
  static final private String HLIST = "HList";
  static final private String HLIST_DEFAULT_FORMAT = "[link][highlight][ex.Title][/highlight][/link]";
  static final private String VLIST = "VList";
  static final private String VLIST_DEFAULT_FORMAT = "<td valign=\"top\">[link][icon][/link]</td>\n<td valign=\"top\">[ex.srclink]{Or}{[ex.thumbicon],[ex.srcicon]}[ex./srclink]</td>\n<td valign=\"top\">[highlight]\n{Or}{[dc.Title],[exp.Title],[ex.Title],Untitled}\n[/highlight]{If}{[ex.Source],<br><i>([ex.Source])</i>}</td>";
  //    static final private String INVISIBLE = "Invisible";
  //static final private String INVISIBLE_DEFAULT_FORMAT = "";
  
  static final private String DOCUMENTHEADING = "DocumentHeading";
  static final private String DOCUMENTHEADING_DEFAULT_FORMAT = "{Or}{[parent(Top):Title],[Title],untitled}<br>";
  
  static final private String DOCUMENTTEXT = "DocumentText";
  static final private String DOCUMENTTEXT_DEFAULT_FORMAT = "[Text]";
  
  static final private String DOCUMENTBUTTONS = "DocumentButtons";
  static final private String DOCUMENTBUTTONS_DEFAULT_FORMAT = "Detach|Highlight";
  static final private String SEARCHTYPES = "SearchTypes";
  static final private String SEARCHTYPES_DEFAULT_FORMAT = "plain,form";
  
  static final private String NAVBAR = "NavigationBar";
  static final private String NAVBAR_OPTION = "pulldown";

  static private HashMap default_format_map = null;
  
  /** The controls used to edit the format commands. */
  private Control controls = null;
  /** A reference to ourselves so inner classes can get at the model. */
  private DOMProxyListModel model = null;
  
  
  /** Constructor. */
  public FormatManager() {
    super(CollectionDesignManager.collect_config.getDocumentElement(), StaticStrings.FORMAT_ELEMENT, new Format());
    this.model = this;
    
    default_format_map = new HashMap();
    default_format_map.put(DATELIST, DATELIST_DEFAULT_FORMAT);
    default_format_map.put(HLIST, HLIST_DEFAULT_FORMAT);
    default_format_map.put(VLIST, VLIST_DEFAULT_FORMAT);
    default_format_map.put(DOCUMENTHEADING, DOCUMENTHEADING_DEFAULT_FORMAT);
    default_format_map.put(DOCUMENTTEXT, DOCUMENTTEXT_DEFAULT_FORMAT);
    default_format_map.put(DOCUMENTBUTTONS, DOCUMENTBUTTONS_DEFAULT_FORMAT);
    default_format_map.put(SEARCHTYPES, SEARCHTYPES_DEFAULT_FORMAT);
    default_format_map.put(NAVBAR, NAVBAR_OPTION);
    default_format_map.put("", "");
    
    DebugStream.println("FormatManager: parsed " + getSize() + " format statements.");
    // Establish all of the format objects, so that classifier indexes are initially correct (subsequent refreshes of the model will be sufficient to keep these up to date, as long as we start with a live reference to a classifier.
    int size = getSize();
    for(int i = 0; i < size; i++) {
      getElementAt(i);
    }
    
    // Ensure the default formats for important format statements are assigned
    if (getFormat(VLIST) == null) {
      addFormat(new Format("",VLIST,VLIST_DEFAULT_FORMAT));
    }
    if (getFormat(HLIST) == null) {
      addFormat(new Format("",HLIST,HLIST_DEFAULT_FORMAT));
    }
    // only if DateList is used
    if (getFormat(DATELIST) == null && CollectionDesignManager.classifier_manager.isDateListClassifierAssigned()) {
      addFormat(new Format("",DATELIST,DATELIST_DEFAULT_FORMAT));
    }
    if (getFormat(DOCUMENTHEADING) == null) {
      addFormat(new Format(DOCUMENTHEADING, "", DOCUMENTHEADING_DEFAULT_FORMAT));
    }
    if (getFormat(DOCUMENTTEXT)== null) {
      addFormat(new Format(DOCUMENTTEXT,"",DOCUMENTTEXT_DEFAULT_FORMAT));
    }
    if (getFormat(DOCUMENTBUTTONS) == null) {
      addFormat(new Format(DOCUMENTBUTTONS,"",DOCUMENTBUTTONS_DEFAULT_FORMAT));
    }
    // only for mgpp and lucene colls
    if (getFormat(SEARCHTYPES)==null && (CollectionDesignManager.index_manager.isMGPP()) || CollectionDesignManager.index_manager.isLucene()) {
      addFormat(new Format(SEARCHTYPES, "", SEARCHTYPES_DEFAULT_FORMAT));
    }
    
    
  }
  
  /** Method to add a new format to this manager.
   * @param format The <strong>Format</strong> to add.
   */
  private void addFormat(Format format) {
    if(!contains(format)) {
      Element element = format.getElement();
      // Locate where we should insert this new classifier.
      Node target_node = CollectionConfiguration.findInsertionPoint(element);
      add(root, format, target_node);
    }
  }
  
  
  public void destroy() {
    if(controls != null) {
      controls.destroy();
      controls = null;
    }
  }
  
  
  private Format getFormat(String name) {
    int model_size = getSize();
    for(int index = 0; index < model_size; index++) {
      Format format = (Format) getElementAt(index);
      if(format.getName().equals(name)) {
        return format;
      }
    }
    return null;
  }
  
  public String getDefaultFormatString(String name){
    String default_str = "";
    try{
      String field_name = name.toUpperCase()+"_DEFAULT_FORMAT";    
      Field field = this.getClass().getDeclaredField(field_name);
      Class fieldType = field.getType();
      default_str = (String) field.get(fieldType);
    } catch (SecurityException e) {
      //System.err.println("Debug: "+e.getMessage());
      default_str = VLIST_DEFAULT_FORMAT;
    } catch (NoSuchFieldException e) {
      //System.err.println("Debug: "+e.getMessage());
      default_str = VLIST_DEFAULT_FORMAT;
    } catch (IllegalArgumentException e) {
      //System.err.println("Debug: "+e.getMessage());
      default_str = VLIST_DEFAULT_FORMAT;
    } catch (IllegalAccessException e) {
      //System.err.println("Debug: "+e.getMessage());		
      default_str = VLIST_DEFAULT_FORMAT;
    }
    return default_str;
  }
  
  /** Method to retrieve this managers controls.
   * @return the Control for this collection.
   */
  public Control getControls() {
    if(controls == null) {
      controls = new FormatControl();
    }
    return controls;
  }
  
  /** Called when the detail mode has changed which in turn may cause several design elements to be available/hidden
   * @param mode the new mode as an int
   */
  public void modeChanged(int mode) {
    
  }
  
  
  /** updates the format model */
  public synchronized void refresh() {
    for(int i = 0; i < getSize(); i++) {
      Format format = (Format) getElementAt(i);
      format.update();
      format = null;
    }
    super.refresh();
  }
  /** Method to remove a format.
   * @param format The <strong>Format</strong> to remove.
   */
  private void removeFormat(Format format) {
    remove(format);
  }
  
  private ArrayList buildFeatureModel() {
    // Rebuild feature model.
    ArrayList feature_model = new ArrayList();
    // Add the set options
    for(int i = 0; i < Format.DEFAULT_FEATURES.length; i++) {
      feature_model.add(new Entry(Format.DEFAULT_FEATURES[i]));
    }
    // Now the classifiers.
    for(int j = 0; j < CollectionDesignManager.classifier_manager.getSize(); j++) {
      feature_model.add(new Entry(CollectionDesignManager.classifier_manager.getClassifier(j)));
    }
    Collections.sort(feature_model);
    return feature_model;
  }
  
  private ArrayList buildPartModel() {
    DebugStream.println("buildPartModel(): replace me with something that reads in a data xml file.");
    ArrayList part_model = new ArrayList();
    part_model.add(new Part("", ""));
    part_model.add(new Part(DATELIST, DATELIST_DEFAULT_FORMAT));
    part_model.add(new Part(HLIST, HLIST_DEFAULT_FORMAT));
    //part_model.add(new Part(INVISIBLE, INVISIBLE_DEFAULT_FORMAT));
    part_model.add(new Part(VLIST, VLIST_DEFAULT_FORMAT));
    return part_model;
  }
    
  private ArrayList buildVariableModel() {
    ArrayList variable_model = new ArrayList();
    variable_model.add(Dictionary.get("CDM.FormatManager.Insert_Variable"));
    variable_model.add("[Text]");
    ArrayList every_metadata_set_element = MetadataSetManager.getEveryMetadataSetElement();
    for (int i = 0; i < every_metadata_set_element.size(); i++) {
      variable_model.add("[" + ((MetadataElement) every_metadata_set_element.get(i)).getFullName() + "]");
    }
    variable_model.add("[link]");
    variable_model.add("[/link]");
    variable_model.add("[icon]");
    variable_model.add("[numleafdocs]");
    variable_model.add("[num]");
    variable_model.add("[parent():_]");
    variable_model.add("[parent(Top):_]");
    variable_model.add("[parent(All'_'):_]");
    variable_model.add("[child():_]");
    variable_model.add("[child(All'_'):_]");
    variable_model.add("[sibling():_]");
    variable_model.add("[sibling(All'_'):_]");
    
    return variable_model;
  }
  
  public class FormatControl
    extends JPanel
    implements Control{
    
    private ArrayList feature_model;
    private ArrayList part_model;
    private ArrayList variable_model;
    private boolean ignore_event = false;
    private boolean ready = false; // Are these controls available to be refreshed
    private CardLayout card_layout;
    private JButton add_button;
    private JButton insert_button;
    private JButton remove_button;
    private JButton default_button;
    private JButton undo_button;
    private JButton redo_button;
    private JCheckBox enabled_checkbox;
    private JComboBox feature_combobox;
    private JComboBox part_combobox;
    private JComboBox variable_combobox;
    private JList format_list;
    private RSyntaxTextArea editor_textarea;
    private JPanel blank_pane;
    private JPanel control_pane;
    private JPanel part_pane;
    private JPanel selection_pane;
    private String view_type;
    private final Dimension FIELD_SIZE = new Dimension(200, 30);
    private final UndoManager undo = new UndoManager();
    private boolean newtext = true;
    private Format previousFormat = null;
    private Format currentFormat = null;
    private boolean fresh = true;
    
    public FormatControl() {
      this.setComponentOrientation(Dictionary.getOrientation());
      feature_model = buildFeatureModel();
      part_model = buildPartModel();
      variable_model = buildVariableModel();
      
      // Create
      JPanel header_pane = new DesignPaneHeader("CDM.GUI.Formats", "formatstatements");
      
      format_list = new JList(model);
      
      selection_pane = new JPanel();
      JPanel feature_pane = new JPanel();
      feature_pane.setComponentOrientation(Dictionary.getOrientation());

      JLabel feature_label = new JLabel(Dictionary.get("CDM.FormatManager.Feature"));
      feature_label.setComponentOrientation(Dictionary.getOrientation());

      feature_combobox = new JComboBox(feature_model.toArray());
      feature_combobox.setComponentOrientation(Dictionary.getOrientation());

      feature_combobox.setOpaque(!Utility.isMac());
      feature_combobox.setPreferredSize(FIELD_SIZE);
      feature_combobox.setEditable(false);
      feature_combobox.setToolTipText(Dictionary.get("CDM.FormatManager.Feature_Tooltip"));
      
      part_pane = new JPanel();
      part_pane.setComponentOrientation(Dictionary.getOrientation());
      JLabel part_label = new JLabel(Dictionary.get("CDM.FormatManager.Part"));
      part_label.setComponentOrientation(Dictionary.getOrientation());
      part_combobox = new JComboBox(part_model.toArray());
      part_combobox.setComponentOrientation(Dictionary.getOrientation());
      part_combobox.setOpaque(!Utility.isMac());
      part_combobox.setPreferredSize(FIELD_SIZE);
      part_combobox.setEditable(false);
      part_combobox.setToolTipText(Dictionary.get("CDM.FormatManager.Part_Tooltip"));
      
      blank_pane = new JPanel();
      
      JPanel center_pane = new JPanel();
      
      card_layout = new CardLayout();
      control_pane = new JPanel();
      
      JPanel blank_pane = new JPanel();
      
      JPanel editor_pane = new JPanel();
      JPanel editor_header_pane = new JPanel();
      JLabel editor_label = new JLabel(Dictionary.get("CDM.FormatManager.Editor"));
      editor_label.setComponentOrientation(Dictionary.getOrientation());
      
      editor_textarea = new RSyntaxTextArea();

      /* Fields specific to RSyntaxQuery inherited class */
      editor_textarea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_HTML);
      editor_textarea.setBracketMatchingEnabled(true);
      editor_textarea.setAnimateBracketMatching(true);
      editor_textarea.setAntiAliasingEnabled(true);
      editor_textarea.setAutoIndentEnabled(true);
      editor_textarea.setPaintMarkOccurrencesBorder(false);

      /* Standard fields to JTextArea */
      editor_textarea.setBackground(Configuration.getColor("coloring.editable_background", false));
      editor_textarea.setCaretPosition(0);
      editor_textarea.setLineWrap(true);
      editor_textarea.setRows(6);
      editor_textarea.setWrapStyleWord(false);
      editor_textarea.setToolTipText(Dictionary.get("CDM.FormatManager.Add_Tooltip"));
      
      variable_combobox = new JComboBox(variable_model.toArray());
      variable_combobox.setComponentOrientation(Dictionary.getOrientation());
      variable_combobox.setOpaque(!Utility.isMac());
      variable_combobox.setToolTipText(Dictionary.get("CDM.FormatManager.Variable_Tooltip"));
      
      insert_button = new GLIButton(Dictionary.get("CDM.FormatManager.Insert"), Dictionary.get("CDM.FormatManager.Insert_Tooltip"));
      
      default_button = new GLIButton(Dictionary.get("CDM.FormatManager.Default"), Dictionary.get("CDM.FormatManager.Default_Tooltip"));
      
      JPanel flag_pane = new JPanel();
      enabled_checkbox = new JCheckBox(Dictionary.get("CDM.FormatManager.Enabled"));
      enabled_checkbox.setComponentOrientation(Dictionary.getOrientation());
      
      JPanel button_pane = new JPanel();
      button_pane.setComponentOrientation(Dictionary.getOrientation());
      add_button = new GLIButton(Dictionary.get("CDM.FormatManager.Add"), Dictionary.get("CDM.FormatManager.Add_Tooltip"));
      add_button.setEnabled(false);
      
      remove_button = new GLIButton(Dictionary.get("CDM.FormatManager.Remove"), Dictionary.get("CDM.FormatManager.Remove_Tooltip"));
      remove_button.setEnabled(false);
      
      undo_button = new GLIButton(Dictionary.get("General.Undo"), Dictionary.get("General.Undo_Tooltip"));
      undo_button.setEnabled(false);
      
      redo_button = new GLIButton(Dictionary.get("General.Redo"), Dictionary.get("General.Redo_Tooltip"));
      redo_button.setEnabled(false);
      
      // Connect
      add_button.addActionListener(new AddListener());
      remove_button.addActionListener(new RemoveListener());
      default_button.addActionListener(new DefaultListener());
      undo_button.addActionListener(new UndoListener());
      redo_button.addActionListener(new RedoListener());
      enabled_checkbox.addActionListener(new EnabledListener());
      feature_combobox.addActionListener(new FeatureListener());
      part_combobox.addActionListener(new PartListener());
      
      editor_textarea.getDocument().addDocumentListener(new EditorListener());
      // Listen for undo and redo events
      editor_textarea.getDocument().addUndoableEditListener(new UndoableEditListener() {
        public void undoableEditHappened(UndoableEditEvent evt) {
          undo.addEdit(evt.getEdit());
        }
      });
      
      
      format_list.addListSelectionListener(new FormatListListener());
      variable_combobox.addActionListener(new VariableListener());
      
      // Layout
      JPanel format_list_pane = new JPanel();
      format_list_pane.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
      format_list_pane.setLayout(new BorderLayout());
      format_list_pane.add(new JScrollPane(format_list), BorderLayout.CENTER);
      
      
      feature_pane.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
      feature_pane.setLayout(new BorderLayout(5,0));
      feature_pane.add(feature_label, BorderLayout.LINE_START);
      feature_pane.add(feature_combobox, BorderLayout.CENTER);
      
      part_pane.setBorder(BorderFactory.createEmptyBorder(2,0,0,0));
      part_pane.setLayout(new BorderLayout(5, 0));
      part_pane.add(part_label, BorderLayout.LINE_START);
      part_pane.add(part_combobox, BorderLayout.CENTER);
      
      
      flag_pane.add(enabled_checkbox);
      
      editor_header_pane.setBorder(BorderFactory.createEmptyBorder(2,0,2,0));
      editor_header_pane.setLayout(new GridLayout(1,3));
      editor_header_pane.add(editor_label);
      
      JPanel rupanel = new JPanel();
      rupanel.setComponentOrientation(Dictionary.getOrientation());
      rupanel.setLayout(new GridLayout(1,2));
      rupanel.add(undo_button);
      rupanel.add(redo_button);
      
      JPanel variable_pane = new JPanel();
      variable_pane.setComponentOrientation(Dictionary.getOrientation());
      variable_pane.setBorder(BorderFactory.createEmptyBorder(2,0,2,0));
      variable_pane.setLayout(new GridLayout(1,3));
      variable_pane.add(new JPanel());
      variable_pane.add(variable_combobox);
      variable_pane.add(rupanel);
      
      editor_pane.setLayout(new BorderLayout());
      editor_pane.add(editor_header_pane, BorderLayout.NORTH);
      editor_pane.add(new JScrollPane(editor_textarea), BorderLayout.CENTER);
      
      selection_pane.setLayout(new BorderLayout());
      selection_pane.add(part_pane, BorderLayout.NORTH);
      selection_pane.add(editor_pane, BorderLayout.CENTER);
      selection_pane.add(variable_pane, BorderLayout.SOUTH);
      
      control_pane.setLayout(card_layout);
      control_pane.add(flag_pane, FLAG);
      control_pane.add(selection_pane, VALUE);
      control_pane.add(blank_pane, BLANK);
      
      
      button_pane.setLayout(new GridLayout(1,3));
      button_pane.add(add_button);
      button_pane.add(remove_button);
      button_pane.add(default_button);
      
      
      center_pane.setLayout(new BorderLayout());
      center_pane.add(feature_pane, BorderLayout.NORTH);
      center_pane.add(control_pane, BorderLayout.CENTER);
      center_pane.add(button_pane, BorderLayout.SOUTH);
      
      setBorder(BorderFactory.createEmptyBorder(0,5,0,0));
      setLayout(new BorderLayout());
      add(header_pane, BorderLayout.NORTH);
      add(format_list_pane, BorderLayout.CENTER);
      add(center_pane, BorderLayout.SOUTH);
      ready = true;
    }
    
    public void destroy() {
    }
    
    
    
    /** Overriden to ensure that the instructions pane is scrolled to the top.
     */
    public void gainFocus() {
      // This is only necessary if the components have been realized
      if(ready) {
        model.refresh();
        
        // Update the feature model, trying to maintain the same selected object
        Object selected_feature = feature_combobox.getSelectedItem();
        feature_model = buildFeatureModel();
        feature_combobox.setModel(new DefaultComboBoxModel(feature_model.toArray()));
        feature_combobox.setSelectedItem(selected_feature);
        
        // Update the variable model,
        variable_model = buildVariableModel();
        variable_combobox.setModel(new DefaultComboBoxModel(variable_model.toArray()));
      }
    }
    
    public void loseFocus() {
      // Force all of the Formats to update their names with the correct values.
      // do we need to do this on loseFocus???
      model.refresh();
    }
    
    public Format getCurrentFormat() {
      return (Format)format_list.getSelectedValue();
      
    }
    
    /** Listens for clicks on the add button, and if the relevant details are provided adds a new format. Note that formats are responsible for codecing the values into something that can be a) stored in a DOM and b) written to file */
    private class AddListener
      implements ActionListener {
      
      public void actionPerformed(ActionEvent event) {
        
        ignore_event = true; // Prevent format_list excetera propagating events
        
        Entry entry = (Entry) feature_combobox.getSelectedItem();
        Object f = entry.getFeature();
        String p = "";
        if (entry.canHavePart()) {
          p = ((Part)part_combobox.getSelectedItem()).getName();
        }
        
        // Add a new format string of the appropriate type
        Format format = null;
        if (view_type.equals(FLAG)) {
          format = new Format(f, p, enabled_checkbox.isSelected());
        } else {
          format = new Format(f, p, editor_textarea.getText());
          
        }
        
        
        addFormat(format);
        existingFormat();
        
        // Update list selection
        format_list.setSelectedValue(format, true);
        format = null;
        p = null;
        f = null;
        entry = null;
        ignore_event = false;
      }
    }
    
    private class EditorListener
      implements DocumentListener {
      
      public void changedUpdate(DocumentEvent e) {
        
        update();
      }
      
      public void insertUpdate(DocumentEvent e) {
        update();
        updateUndo("insert");
        
      }
      
      public void removeUpdate(DocumentEvent e) {
        update();
        updateUndo("remove");
        
      }
      
      private void updateUndo(String from){
        
        if (!newtext){
          undo_button.setEnabled(true);
        }
        
        if (editor_textarea.getText().length()!=0 && newtext){
          newtext = false;
        }
      }
      
      public void update() {
	
        // Determine if replace should be enabled
        if(!format_list.isSelectionEmpty()) {
          Format format = (Format)format_list.getSelectedValue();
          boolean shouldSave = !format.isParamType();
          
          if (shouldSave){
	      // Changed so DocumentButtons statement can be set to empty 
	      // to get rid of the Detach and NOHIGHLIGHT buttons 
	      //String format_str = editor_textarea.getText().trim();
	      //format_str = (format_str.equals(""))? getDefaultFormatString(format.getName()) : format_str;
            format.setValue(editor_textarea.getText().trim());
            model.refresh((DOMProxyListEntry)format);
          }
          
        } else {
          add_button.setEnabled(false);
          
        }
        
      }
    }
    
    private class EnabledListener
      implements ActionListener {
      
      public void actionPerformed(ActionEvent event) {
        // If there is a current format selected, and the value of enable_checkbox is now different than to value in it, then enable to replace button.
        if(!format_list.isSelectionEmpty()) {
          Format format = (Format)format_list.getSelectedValue();
          boolean shouldSave = format.isParamType();
          if (shouldSave){
            format.setState(enabled_checkbox.isSelected());
            model.refresh((DOMProxyListEntry)format);
          }
        }
        
        // Thats it. Add would have been enabled upon feature/part selection depending if no existing format, um, existed.
      }
    }
    
    private class FeatureListener
      implements ActionListener {
      public void actionPerformed(ActionEvent event) {
        undo_button.setEnabled(false);
        redo_button.setEnabled(false);
        default_button.setEnabled(true);
        newtext = true;
        
        if(!ignore_event) {
          ignore_event = true;
          Entry entry = (Entry) feature_combobox.getSelectedItem();
          
          // Step one: reset part
          if (entry.canHavePart()) {
            // update AffectedComponents according to current entry             
            part_model = entry.getPartModel();
            part_combobox.setModel(new DefaultComboBoxModel(part_model.toArray()));
            part_combobox.updateUI();            
            
            part_combobox.setEnabled(true);
            part_combobox.setSelectedIndex(part_combobox.getModel().getSize()-1);                        
            
          } else {
            part_combobox.setEnabled(false);
            part_combobox.setSelectedIndex(0);
          }
          // Step two: the rest
          
          String name = entry.toString();
          // Add is only enabled if there isn't already a format for the choosen feature and part. Create a dummy format and test if itsa already in the model
          Object f = entry.getFeature();
          Part part = (Part)part_combobox.getSelectedItem();
          String pname = part.getName();
          // You can never add anything to blank-blank
          if(f.toString().length() == 0 && pname.length() == 0) {
            add_button.setEnabled(false);
            remove_button.setEnabled(false);
          } else {
            
            Format format = getFormat(Format.generateName(f, pname));
            // If there is an existing feature, select it, and use it to determine what controls are visible
            
            if(format != null) {
              ///ystem.err.println("There is an existing format!");
              format_list.setSelectedValue(format, true);
              // Now use type to determine what controls are visible, and what have initial values.
              if(format.isParamType()) {
                ///ystem.err.println("Flag type");
                // Flags first.
                ///election_pane.remove(part_pane);
                card_layout.show(control_pane, FLAG);
                view_type = FLAG;
                // Initial value
                enabled_checkbox.setSelected(format.getState());
              } else {
                ///ystem.err.println("Value type");
                ///election_pane.add(part_pane);
                card_layout.show(control_pane, VALUE);
                view_type = VALUE;
                // Initial value
                
                editor_textarea.setText(format.getValue());
                editor_textarea.setCaretPosition(0);
                
              }
              existingFormat();
              control_pane.updateUI();
            }
            // Otherwise there is no existing format, so we proceed by checking against the feature name
            else {
              ///ystem.err.println("No existing format");
              format_list.clearSelection();
              if(Format.isParamType(name)) {
                ///ystem.err.println("Flag type");
                // Flags first.
                ///election_pane.remove(part_pane);
                card_layout.show(control_pane, FLAG);
                view_type = FLAG;
                // Initial value
                enabled_checkbox.setSelected(false);
                enabled_checkbox.setEnabled(false);
              } else {
                ///ystem.err.println("Value type");
                ///election_pane.add(part_pane);
                card_layout.show(control_pane, VALUE);
                view_type = VALUE;
                // Initial value
                
                String feature_default_format = (String) default_format_map.get(f.toString());
                if (feature_default_format != null) {
                  
                  editor_textarea.setText(feature_default_format);
                } else {
                  editor_textarea.setText(part.getDefaultFormat());
                }
                editor_textarea.setCaretPosition(0);
                
              }
              newFormat();
            }
            format = null;
            name = null;
          }
          part = null;
          pname = null;
          f = null;
          name = null;
          entry = null;
          ignore_event = false;
        }
        undo.discardAllEdits();
      }
    }
    
    private class FormatListListener
      implements ListSelectionListener {
      public void valueChanged(ListSelectionEvent event) {
        undo_button.setEnabled(false);
        redo_button.setEnabled(false);
        default_button.setEnabled(true);
        newtext = true;
        
        if(!ignore_event && !event.getValueIsAdjusting()) {
          
          if(!format_list.isSelectionEmpty()) {
            existingFormat();
            ignore_event = true;
            Format format = (Format)format_list.getSelectedValue();
            // Try to match the target, remembering the entries within are Entry's
            Entry an_entry = new Entry(format.getFeature());            
            feature_combobox.setSelectedItem(an_entry);            
            // If we didn't match anything, add it and select it again
            Entry result_entry = (Entry) feature_combobox.getSelectedItem();
            if(!an_entry.equals(result_entry)) {
              feature_combobox.insertItemAt(an_entry, feature_combobox.getItemCount());
              feature_combobox.setSelectedItem(an_entry);
            }
            
            // Now use type to determine what controls are visible, and what have initial values.
            ///ystem.err.println("Determine the correct type.");
            if(format.isParamType()) {
              ///ystem.err.println("Flag type - remove part");
              // Flags first.
              ///SwingUtilities.invokeLater(new GUIChangeTask(/election_pane, part_pane, blank_pane, false));
              card_layout.show(control_pane, FLAG);
              view_type = FLAG;
              // Initial value
              enabled_checkbox.setSelected(format.getState());
            } else {
              ///ystem.err.println("Value type - show part");
              ///SwingUtilities.invokeLater(new GUIChangeTask(/election_pane, part_pane, blank_pane, true));
              card_layout.show(control_pane, VALUE);
              view_type = VALUE;
              // Initial values
              // do we have a part?
              if (format.canHavePart()) {
                part_combobox.setEnabled(true);
                
                // Should update the part list and re-render the part ComboBox here
                part_model = result_entry.getPartModel();
                part_combobox.setModel(new DefaultComboBoxModel(part_model.toArray()));
                part_combobox.updateUI();      
                
                // Try to match the part.
                String part_entry = format.getPart();
                // Set Selected Item doesn't work so I'll do this manually
                boolean found = false;
                for(int i=0; i < part_combobox.getItemCount(); i++) {
                  Part a_part = (Part) part_combobox.getItemAt(i);
                  if(a_part.equals(part_entry)) {
                    part_combobox.setSelectedItem(a_part);
                    found = true;
                  }
                  a_part = null;
                }
                // If we didn't match anything, add it and select it again
                if(!found) {
                  Part a_part = new Part(part_entry, "");
                  part_combobox.insertItemAt(a_part, part_combobox.getItemCount());
                  part_combobox.setSelectedItem(a_part);
                  
                }
              } else {
                part_combobox.setEnabled(false);
                part_combobox.setSelectedIndex(0);
              }
              editor_textarea.setText(format.getValue());
              editor_textarea.setCaretPosition(0);
              
            }
            
            //control_pane.updateUI();
            ignore_event = false;
          }
          
        }
        undo.discardAllEdits();
      }
      
    }
    
    private void newFormat(){
      editor_textarea.setEditable(false);
      editor_textarea.setBackground(Color.lightGray);
      editor_textarea.setToolTipText(Dictionary.get("CDM.FormatManager.Editor_Disabled_Tooltip"));
      
      enabled_checkbox.setEnabled(false);
      undo_button.setEnabled(false);
      redo_button.setEnabled(false);
      variable_combobox.setEnabled(false);
      add_button.setEnabled(true);
      remove_button.setEnabled(false);
      default_button.setEnabled(false);
      
    }
    
    private void existingFormat(){
      editor_textarea.setEditable(true);
      editor_textarea.setBackground(Color.white);
      editor_textarea.setToolTipText(Dictionary.get("CDM.FormatManager.Editor_Tooltip"));
      enabled_checkbox.setEnabled(true);
      variable_combobox.setEnabled(true);
      add_button.setEnabled(false);
      remove_button.setEnabled(true);
      default_button.setEnabled(true);
      
    }
    
    private class VariableListener
      implements ActionListener {
      public void actionPerformed(ActionEvent event) {
        int selected_index = variable_combobox.getSelectedIndex();
        if (selected_index == 0) return;
        String selected_value = (String)variable_combobox.getSelectedItem();
        editor_textarea.insert(selected_value, editor_textarea.getCaretPosition());
        undo_button.setEnabled(true);
        variable_combobox.setSelectedIndex(0);
      }
      
    }
    
    private class PartListener
      implements ActionListener {
      public void actionPerformed(ActionEvent event) {
        undo_button.setEnabled(false);
        redo_button.setEnabled(false);
        default_button.setEnabled(true);
        newtext = true;
        if(!ignore_event) {
          // Add is only enabled if there isn't already a format for the choosen feature and part. Create a dummy format and test if its already in the model
          Entry entry = (Entry) feature_combobox.getSelectedItem();
          Object f = entry.getFeature();
          Part part = (Part) part_combobox.getSelectedItem();
          String pname = part.getName();
          // You can never add anything to blank-blank
          if(f.toString().length() == 0 && pname.length() == 0) {
            add_button.setEnabled(false);
            remove_button.setEnabled(false);
          } else {
            String name = Format.generateName(f, pname);
            Format format = getFormat(name);
            // If there is an existing feature, select it.
            if(format != null) {
              format_list.setSelectedValue(format, true);
              // Now use type to determine what controls are visible, and what have initial values.
              if(format.isParamType()) {
                // Flags first.
                ///election_pane.remove(part_pane);
                card_layout.show(control_pane, FLAG);
                view_type = FLAG;
                // Initial value
                enabled_checkbox.setSelected(format.getState());
              } else {
                ///election_pane.add(part_pane);
                card_layout.show(control_pane, VALUE);
                view_type = VALUE;
                // Initial value
                
                editor_textarea.setText(format.getValue());
                editor_textarea.setCaretPosition(0);
                
              }
              control_pane.updateUI();
              existingFormat();
            } else {
              format_list.clearSelection();
              if(Format.isParamType(name)) {
                // Flags first.
                ///election_pane.remove(part_pane);
                card_layout.show(control_pane, FLAG);
                view_type = FLAG;
                // Initial value
                enabled_checkbox.setSelected(false);
                enabled_checkbox.setEnabled(false);
              } else {
                ///election_pane.add(part_pane);
                card_layout.show(control_pane, VALUE);
                view_type = VALUE;
                // Initial value
                
                editor_textarea.setText(part.getDefaultFormat());
                editor_textarea.setCaretPosition(0);
                
              }
              newFormat();
            }
            format = null;
            name = null;
          }
          
          pname = null;
          part = null;
          f = null;
          entry = null;
        }
        undo.discardAllEdits();
      }
    }
    
    private class RemoveListener
      implements ActionListener {
      
      public void actionPerformed(ActionEvent event) {
        if (!format_list.isSelectionEmpty()) {
          // Remove the current format
          removeFormat((Format)format_list.getSelectedValue());
          
          // Change buttons
          add_button.setEnabled(true);
          newFormat();
        }
      }
    }
    
    
    
    private class DefaultListener
      implements ActionListener {
      
      public void actionPerformed(ActionEvent event) {
	  
	  // ensure that the user didn't accidentally press the Reset to Default button
	  int user_choice = JOptionPane.showConfirmDialog(Gatherer.g_man, Dictionary.get("CDM.FormatManager.Default_Warning"), Dictionary.get("General.Warning"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
	  if(user_choice == JOptionPane.CANCEL_OPTION) {
	      return;
	  }
	  
        newtext = false;
        if(!ignore_event) {
          Entry entry = (Entry) feature_combobox.getSelectedItem();
          Object f = entry.getFeature();
          String name ="";
          String pname="";
          Part part = null;
          
          if (entry.canHavePart()){
            part = (Part) part_combobox.getSelectedItem();
            pname = part.getName();
            name = Format.generateName(f, pname);
          } else{
            name = entry.toString();
          }
          
          Format format = getFormat(name);
          // If there is an existing feature, select it.
          if(format != null) {
            remove_button.setEnabled(true);
          } else {
            
            add_button.setEnabled(true);
          }//endif (format !=null)
          
          if(Format.isParamType(name)) {
            // Flags first.
            card_layout.show(control_pane, FLAG);
            view_type = FLAG;
            // Initial value
            enabled_checkbox.setSelected(false);
          } else {
            card_layout.show(control_pane, VALUE);
            view_type = VALUE;
            // Initial value
            if (pname !=null && pname.length()!=0 ){
              editor_textarea.setText((String) default_format_map.get(pname));
              editor_textarea.setCaretPosition(0);
            } else{
              editor_textarea.setText((String) default_format_map.get(name));
              editor_textarea.setCaretPosition(0);
            }
          }
          
	}
      }
      
    }
    
    private class UndoListener
      implements ActionListener {
      
      public void actionPerformed(ActionEvent event) {
        try {
          if (undo.canUndo()) {
            int pos = editor_textarea.getCaretPosition();
            redo_button.setEnabled(true);
            undo.undo();
            if (pos > 0)
              editor_textarea.setCaretPosition(pos-1);
            else
              editor_textarea.setCaretPosition(pos);
          }
          if (!undo.canUndo()){
            undo_button.setEnabled(false);
          } else{
            undo_button.setEnabled(true);
          }
          
        } catch (Exception e) {
          
        }
      }
    }
    
    private class RedoListener
      implements ActionListener {
      public void actionPerformed(ActionEvent evt) {
        try {
          if (undo.canRedo()) {
            int pos = editor_textarea.getCaretPosition();
            undo.redo();
            editor_textarea.setCaretPosition(pos);
          }
          if (!undo.canRedo()){
            redo_button.setEnabled(false);
          } else{
            redo_button.setEnabled(true);
          }
          
        } catch (Exception e) {}
      }
    }
    
  }
  
  /** This object provides a wrapping around an entry in the format target control, which is tranparent as to whether it is backed by a String or a Classifier. */
  private class Entry
    implements Comparable {
    private Classifier classifier = null;
    private String text = null;
    
    public Entry(Object object) {
      if(object instanceof Classifier) {
        classifier = (Classifier)object;
      } else if(object instanceof String) {
        text = (String)object;
      } else {
        text = "";
      }
    }
    
    public Entry(String text) {
      this.text = text;
    }
    
    public boolean canHavePart() {
      if (classifier !=null) return true;
      return Format.canHavePart(text);
    }
    
    public int compareTo(Object object) {
      if(object == null) {
        return 1;
      }
      if(toString() == null) {
        return -1;
      } else {
        String object_str = object.toString();
        if(object_str == null) {
          return 1;
        }
        return toString().compareTo(object_str);
      }
    }
    
    public boolean equals(Object object) {
      if(compareTo(object) == 0) {
        return true;
      }
      return false;
    }
    
    public Classifier getClassifier() {
      return classifier;
    }
    
    public Object getFeature() {
      if(classifier != null) {
        return classifier;
      }
      
      if (text.startsWith("<html>")){
        return  "";
      }
      return text;
    }
    
    public ArrayList getPartModel(){
      DebugStream.println("getPartModel(): get appopriate affected components of this classifier");
      String classifier_name = classifier == null ? "" : classifier.toString();
      if(!classifier_name.equals("")){
        classifier_name = classifier_name.substring(0, classifier_name.indexOf("-")).trim();        
      }      
      ArrayList part_model = new ArrayList();
      // DateList only has DateList component
      if(classifier_name.equals("DateList")){
        part_model.add(new Part(DATELIST, DATELIST_DEFAULT_FORMAT));
      } 
      // Other Format Entries, display the default Affected Components
      else if(classifier_name.equals("")){      
        part_model = buildPartModel();      
      }
      // Other Classifiers have HList and VList as Affected Component
      else {
        part_model.add(new Part(HLIST, HLIST_DEFAULT_FORMAT));
        part_model.add(new Part(VLIST, VLIST_DEFAULT_FORMAT));
      }
      return part_model;
    }
    
    public String toString() {
      if(classifier != null) {
        // Return the classifier - with its CL index shown
        return classifier.getPositionString() + StaticStrings.COLON_CHARACTER + StaticStrings.SPACE_CHARACTER + classifier.toString();
      }
      if (text.equals("")) {
        return "<html><body><i>"+Dictionary.get("CDM.FormatManager.AllFeatures")+"</i></body></html>";
      }
      return text;
    }
  }
  
    /*
      private class GUIChangeTask
      implements Runnable {
      private boolean to_add;
      private JPanel child;
      private JPanel parent;
      private JPanel replacement;
     
      public GUIChangeTask(JPanel parent, JPanel child, JPanel replacement, boolean to_add) {
      this.child = child;
      this.parent = parent;
      this.replacement = replacement;
      this.to_add = to_add;
      }
     
      public void run() {
      if(to_add) {
      parent.remove(replacement);
      parent.add(child);
      parent.updateUI();
      }
      else {
      parent.remove(child);
      parent.add(replacement);
      parent.updateUI();
      }
      }
      }
     */
  
  /** This class encapsulates all of the information associated with a certain component part of a feature within a html page returned from the receptioninst. */
  private class Part
    implements Comparable {
    /** The default format string for this part */
    private String default_format = null;
    /** The name of this part */
    private String name = null;
    /** Constructor - must be provided with all of the details of a part as there are no other setter methods.
     * @param name the name of this part
     * @param default_format the default format string for this part
     */
    public Part(String name, String default_format) {
      this.default_format = default_format;
      this.name = name;
    }
    /** Compare this part to another object in terms of ordering
     * @param obj the other Object
     * @return <0 if the object is before, 0 if equal to, and >0 if the object is after this part
     */
    public int compareTo(Object obj) {
      if (obj instanceof Part) {
        return name.compareTo(((Part)obj).getName());
      }
      return name.compareTo(obj.toString());
    }
    
    /** Determine if the part is equivelent to some other object
     * @param obj the other Object
     * @return true is the two objects are equal
     */
    public boolean equals(Object obj) {
      if (obj instanceof Part) {
        return name.equals(((Part)obj).getName());
      }
      return name.equals(obj.toString());
    }
    
    /** Retrieve the default format string for this part
     * @return the default format String
     */
    public String getDefaultFormat() {
      // Retrieve the format for the super format - either VList or HList
      Format default_format_object = getFormat(name);
      if(default_format_object != null) {
        return default_format_object.getValue();
      } else {
        return this.default_format;
      }
    }
    /** Retrieve the name of this part
     * @return the name as a String
     */
    public String getName() {
      return name;
    }
    /** Produce a string representation of this part, which in this case is simply the name again
     * @return the name as a String
     */
    public String toString() {
      if (name.equals("")) {
        return "<html><body><i>"+Dictionary.get("CDM.FormatManager.AllParts")+"</i></body></html>";
      }
      return name;
    }
  }
}
