/**
 *#########################################################################
 *
 * 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.io.*;
import java.net.*;
import java.util.*;
import java.util.jar.*;
import javax.swing.*;
import javax.swing.event.*;
import org.apache.xerces.parsers.*;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.DebugStream;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.greenstone.Classifiers;
import org.greenstone.gatherer.gui.DesignPaneHeader;
import org.greenstone.gatherer.gui.GComboBox;
import org.greenstone.gatherer.gui.GLIButton;
import org.greenstone.gatherer.gui.TestingPreparation;
import org.greenstone.gatherer.util.JarTools;
import org.greenstone.gatherer.util.StaticStrings;
import org.greenstone.gatherer.util.Utility;
import org.greenstone.gatherer.util.XMLTools;
import org.w3c.dom.*;
import org.xml.sax.*;

/** This class is responsible for keeping track of all the classifiers assigned to this collection, and providing methods for adding and removing them.
 * @author John Thompson, Greenstone Digital Library, University of Waikato
 * @version 2.3
 */
public class ClassifierManager
extends DOMProxyListModel {

    private DatabaseTypeManager database_type_manager = null;
    private String database_type = null;

    /** The controls for editing the contents of this manager. */
    private Control controls = null;
    
    private DOMProxyListModel model;
    private Classifier default_classifier = null;

    /** Constructor.
     * @see org.greenstone.gatherer.cdm.DynamicListModel
     * @see org.greenstone.gatherer.collection.CollectionManager
     */
    public ClassifierManager () {
        super (CollectionDesignManager.collect_config.getDocumentElement (), StaticStrings.CLASSIFY_ELEMENT, new Classifier ());
        this.model = this;
        DebugStream.println ("ClassifierManager: " + getSize () + " classifiers parsed.");

	database_type_manager = new DatabaseTypeManager();
	database_type = database_type_manager.getDatabaseType();
        
        // Force the assigned classifiers to be loaded and cached now
        for (int i = 0; i < getSize (); i++) {
            getElementAt (i);
        }
    }
    
    
    /** Retrieve a list of the classifiers that are available to be added to the collection. */
    private Object[] getAvailableClassifiers () {
        ArrayList available = new ArrayList ();
        
        // Add all the non-abstract core Greenstone classifiers
        ArrayList classifiers_list = Classifiers.getClassifiersList ();
        for (int i = 0; i < classifiers_list.size (); i++) {
            Classifier classifier = (Classifier) classifiers_list.get (i);
            if (!classifier.isAbstract ()) {
                available.add (classifier);
            }
	    if (classifier.toString().equals("List")) {
		default_classifier = classifier;
	    }
        }
        
        // Sort the available classifiers into alphabetical order
        Collections.sort (available);
        
        return available.toArray ();
    }
    
    
    /** Method to assign a classifier (i.e., add a new classifier onto the list).
     * @param classifier The base <strong>Classifier</strong> to assign.
     * @see org.greenstone.gatherer.cdm.DynamicListModel
     */
    private void assignClassifier (Classifier classifier) {
        if(!contains (classifier)) {
            Element element = classifier.getElement ();
            // Locate where we should insert this new classifier.
            Node target_node = CollectionConfiguration.findInsertionPoint (element);
            add (root, classifier, target_node);

            // tell the format manager to update the names of its format statements
            Gatherer.c_man.getCollection ().cdm.format_manager.refresh ();
        }
    }
    
    
    /** Destructor. */
    public void destroy () {
        if (controls != null) {
            controls.destroy ();
            controls = null;
        }
    }
    
    
    /** Method to retrieve the classifier with the given index.
     * @param index The index of the desired classifier as an <i>int</i>.
     * @return The requested Classifier or <i>null</i> if no such classifier exists.
     */
    public Classifier getClassifier (int index) {
        if(0 <= index && index < getSize ()) {
            return (Classifier) getElementAt (index);
        }
        return null;
    }
    
    /** Method to retrieve the control for this manager.
     * @return the Control for editing classifiers
     */
    public Control getControls () {
        if(controls == null) {
            // Build controls
            this.controls = new ClassifierControl ();
        }
        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) {
        
    }
    
    
    
    /** Determine if the Phind classifier has been assigned.
     * @return true if it has, false otherwise
     */
    public boolean isPhindClassifierAssigned () {
        for(int i = 0; i < getSize (); i++) {
            Classifier classifier = (Classifier) getElementAt (i);
            if(classifier.getName ().equalsIgnoreCase (StaticStrings.PHIND_CLASSIFIER)) {
                return true;
            }
            classifier = null;
        }
        return false;
    }
    
    /** Determine if a DateList classifier has been assigned
     * @return true if it has, false otherwise
     */
    public boolean isDateListClassifierAssigned () {
        for(int i = 0; i < getSize (); i++) {
            Classifier classifier = (Classifier) getElementAt (i);
            if(classifier.getName ().equalsIgnoreCase (StaticStrings.DATELIST_CLASSIFIER)) {
                return true;
            }
            classifier = null;
        }
        return false;
        
    }
    /** Method to move a classifier in the list order.
     * @param classifier the Classifier you want to move.
     * @param direction true to move the classifier up, false to move it down.
     * @param all true to move to move all the way, false for a single step.
     */
    private void moveClassifier (Classifier classifier, boolean direction, boolean all) {
        if(getSize () < 2) {
            DebugStream.println ("Not enough classifiers to allow moving.");
            return;
        }
        if(all) {
            // Move to top
            if(direction) {
                // Remove the moving classifier
                remove (classifier);
                // Retrieve the first classifier
                Classifier first_classifier = (Classifier) getElementAt (0);
                // Add the moving classifier before the first classifier
                addBefore (classifier, first_classifier);
                first_classifier = null;
            }
            else {
                // Remove the moving classifier
                remove (classifier);
                // And add after last classifier
                add (getSize (), classifier);
            }
        }
        else {
            // Try to move the classifier one step in the desired direction.
            int index = indexOf (classifier);
            ///ystem.err.println("Index of " + classifier + " = " + index);
            if(direction) {
                index--;
                if(index < 0) {
                    String args[] = new String[2];
                    args[0] = Dictionary.get ("CDM.ClassifierManager.Classifier");
                    args[1] = classifier.getName ();
                    JOptionPane.showMessageDialog (Gatherer.g_man, Dictionary.get ("CDM.Move.At_Top", args), Dictionary.get ("CDM.Move.Title"), JOptionPane.ERROR_MESSAGE);
                    return;
                }
                remove (classifier);
                add (index, classifier);
            }
            else {
                index++;
                if(index >= getSize ()) {
                    String args[] = new String[2];
                    args[0] = Dictionary.get ("CDM.ClassifierManager.Classifier_Str");
                    args[1] = classifier.getName ();
                    JOptionPane.showMessageDialog (Gatherer.g_man, Dictionary.get ("CDM.Move.At_Bottom", args), Dictionary.get ("CDM.Move.Title"), JOptionPane.ERROR_MESSAGE);
                    return;
                }
                remove (classifier);
                add (index, classifier);
            }
        }
        
        // tell the format manager to update the names of its format statements
        Gatherer.c_man.getCollection ().cdm.format_manager.refresh ();
    }
    
    /** This method removes an assigned classifier. I was tempted to call it unassign, but remove is more consistant. Note that there is no way to remove a classifier from the library.
     * @param classifier The Classifier to remove
     * @see org.greenstone.gatherer.cdm.DynamicListModel
     */
    private void removeClassifier (Classifier classifier) {
        remove (classifier);
        // tell the format manager to update the names of its format statements
        Gatherer.c_man.getCollection ().cdm.format_manager.refresh ();
        
    }
    // When remove, move, and add(assign) classifiers, this method has to be called to get the feature combobox
    // on the 'format feature' panel of the 'format' panel up to date.
//    private void informFormatManager() {
//        //this is really to call the gainFocus() method
//        CollectionDesignManager.format_manager.getControls();
//    }
    
    /** A class which provides controls for assigned and editing classifiers. */
    private class ClassifierControl
    extends JPanel
    implements Control, DatabaseTypeManager.DatabaseTypeListener {
        /** A combobox containing all of the known classifiers, including those that may have already been assigned. */
        private JComboBox classifier_combobox = null;
        /** Button for adding classifiers. */
        private JButton add = null;
        /** Button for configuring the selected classifier. */
        private JButton configure = null;
        private JButton move_down_button;
        private JButton move_up_button;
        
        /** Button to remove the selected classifier. */
        private JButton remove = null;

        private JPanel database_type_panel = (JPanel)database_type_manager.getControls();
        
        /** A list of assigned classifiers. */
        private JList classifier_list = null;
        
        /** Constructor.
         * @see org.greenstone.gatherer.cdm.ClassifierManager.ClassifierControl.AddListener
         * @see org.greenstone.gatherer.cdm.ClassifierManager.ClassifierControl.ConfigureListener
         * @see org.greenstone.gatherer.cdm.ClassifierManager.ClassifierControl.RemoveListener
         */
        public ClassifierControl () {
            // Create
            this.setComponentOrientation(Dictionary.getOrientation());
            add = new GLIButton (Dictionary.get ("CDM.ClassifierManager.Add"), Dictionary.get ("CDM.ClassifierManager.Add_Tooltip"));
            
            JPanel button_pane = new JPanel ();
            button_pane.setComponentOrientation(Dictionary.getOrientation());
            JPanel central_pane = new JPanel ();
            central_pane.setComponentOrientation(Dictionary.getOrientation());
            
            configure = new GLIButton (Dictionary.get ("CDM.ClassifierManager.Configure"), Dictionary.get ("CDM.ClassifierManager.Configure_Tooltip"));
            configure.setEnabled (false);
            
            JPanel header_pane = new DesignPaneHeader ("CDM.GUI.Classifiers", "classifiers");
            
            ClassifierComboboxListener ccl = new ClassifierComboboxListener ();
            classifier_combobox = new JComboBox (getAvailableClassifiers ());
            classifier_combobox.setComponentOrientation(Dictionary.getOrientation());
            classifier_combobox.setOpaque (!Utility.isMac ());
            classifier_combobox.setEditable (false);
            if(classifier_combobox.getItemCount () > 0) {
		if (default_classifier!=null) {
		    classifier_combobox.setSelectedItem(default_classifier);
		} else {
		    classifier_combobox.setSelectedIndex (0);
		}
                ccl.itemStateChanged (new ItemEvent (classifier_combobox, 0, null, ItemEvent.SELECTED));
            }
            
            JLabel classifier_label = new JLabel (Dictionary.get ("CDM.ClassifierManager.Classifier"));
            classifier_label.setComponentOrientation(Dictionary.getOrientation());
            
            classifier_list = new JList (model);
            classifier_list.setComponentOrientation(Dictionary.getOrientation());
            
            classifier_list.setOpaque (true);
            classifier_list.setSelectionMode (ListSelectionModel.SINGLE_SELECTION);
            JLabel classifier_list_label = new JLabel (Dictionary.get ("CDM.ClassifierManager.Assigned"));
            classifier_list_label.setComponentOrientation(Dictionary.getOrientation());
            
            classifier_list_label.setOpaque (true);
            
            JPanel classifier_list_pane = new JPanel ();
            classifier_list_pane.setComponentOrientation(Dictionary.getOrientation());
            
            JPanel classifier_pane = new JPanel ();
            classifier_pane.setComponentOrientation(Dictionary.getOrientation());
            remove = new GLIButton (Dictionary.get ("CDM.ClassifierManager.Remove"), Dictionary.get ("CDM.ClassifierManager.Remove_Tooltip"));
            remove.setEnabled (false);
            
            JPanel temp = new JPanel (new BorderLayout ());
            temp.setComponentOrientation(Dictionary.getOrientation());
            
            JPanel move_button_pane = new JPanel ();
            move_button_pane.setComponentOrientation(Dictionary.getOrientation());
            
            move_up_button = new GLIButton (Dictionary.get ("CDM.Move.Move_Up"), JarTools.getImage ("arrow-up.gif"), Dictionary.get ("CDM.Move.Move_Up_Tooltip"));
            move_up_button.setEnabled (false);
            
            move_down_button = new GLIButton (Dictionary.get ("CDM.Move.Move_Down"), JarTools.getImage ("arrow-down.gif"), Dictionary.get ("CDM.Move.Move_Down_Tooltip"));
            move_down_button.setEnabled (false);
            
            // Listeners
            add.addActionListener (new AddListener ());
            classifier_combobox.addItemListener (ccl);
            configure.addActionListener (new ConfigureListener ());
            remove.addActionListener (new RemoveListener ());
            remove.addActionListener (CollectionDesignManager.buildcol_change_listener);
            classifier_list.addMouseListener (new ClickListener ());
            classifier_list.addListSelectionListener (new ListListener ());
            ccl = null;
            
            MoveListener ml = new MoveListener ();
            move_down_button.addActionListener (ml);
            move_down_button.addActionListener (CollectionDesignManager.buildcol_change_listener);
            move_up_button.addActionListener (ml);
            move_up_button.addActionListener (CollectionDesignManager.buildcol_change_listener);
            
            // Layout
            JPanel tmp;
            move_button_pane.setLayout (new GridLayout (4,1));
            move_button_pane.add (move_up_button);
            tmp = new JPanel ();
            tmp.setComponentOrientation(Dictionary.getOrientation());
            move_button_pane.add (tmp);
            tmp = new JPanel ();
            tmp.setComponentOrientation(Dictionary.getOrientation());
            move_button_pane.add (tmp);
            move_button_pane.add (move_down_button);
            
            classifier_list_label.setBorder (BorderFactory.createEmptyBorder (0,2,0,2));
            
            classifier_list_pane.setLayout (new BorderLayout ());
            classifier_list_pane.add (classifier_list_label, BorderLayout.NORTH);
            classifier_list_pane.add (new JScrollPane (classifier_list), BorderLayout.CENTER);
            classifier_list_pane.add (move_button_pane, BorderLayout.LINE_END);
            
            classifier_label.setBorder (BorderFactory.createEmptyBorder (0,0,5,0));
            
            classifier_pane.setBorder (BorderFactory.createEmptyBorder (5,0,5,0));
            classifier_pane.setLayout (new BorderLayout (5,0));
            classifier_pane.add (classifier_label, BorderLayout.LINE_START);
            classifier_pane.add (classifier_combobox, BorderLayout.CENTER);
            
            button_pane.setLayout (new GridLayout (1, 3));
            button_pane.add (add);
            button_pane.add (configure);
            button_pane.add (remove);
            
            temp.add (classifier_pane, BorderLayout.NORTH);
            temp.add (button_pane, BorderLayout.SOUTH);
            
            central_pane.setBorder (BorderFactory.createEmptyBorder (5,0,0,0));
            central_pane.setLayout (new BorderLayout ());
            central_pane.add (database_type_panel, BorderLayout.NORTH);
            central_pane.add (classifier_list_pane, BorderLayout.CENTER);
            central_pane.add (temp, BorderLayout.SOUTH);
            
            setBorder (BorderFactory.createEmptyBorder (0,5,0,0));
            setLayout (new BorderLayout ());
            add (header_pane, BorderLayout.NORTH);
            add (central_pane, BorderLayout.CENTER);

	    TestingPreparation.setNamesRecursively(this);
        }
        
        /** Method which acts like a destructor, tidying up references to persistant objects.
         */
        public void destroy () {
            add = null;
            classifier_combobox = null;
            classifier_list = null;
            configure = null;
            //instructions = null;
            remove = null;
        }
        
        public void gainFocus () {
        }
        
        public void loseFocus () {
        }
        

	public void databaseTypeChanged(String new_database_type)
	{
	    if(database_type.equals(new_database_type)){
		return;
	    }
	    database_type = new_database_type;
	}
        
        private class AddListener
        implements ActionListener {
            public void actionPerformed (ActionEvent event) {
                if (classifier_combobox.getSelectedItem () != null) {
                    // This must be done on a new thread for the remote building code
                    new AddClassifierTask (classifier_combobox.getSelectedItem ().toString ()).start ();
                }
            }
        }
        
        
        private class AddClassifierTask
        extends Thread {
            private String classifier_name;
            
            public AddClassifierTask (String classifier_name) {
                this.classifier_name = classifier_name;
            }
            
            public void run () {
                // Retrieve the classifier
                Classifier classifier = Classifiers.getClassifier (classifier_name, true);
                if (classifier == null) {
                    System.err.println ("Error: getClassifier() returned null.");
                    return;
                }
                
                // Create a new element in the DOM
                Element new_classifier_element = CollectionConfiguration.createElement (StaticStrings.CLASSIFY_ELEMENT);
                new_classifier_element.setAttribute (StaticStrings.TYPE_ATTRIBUTE, classifier.getName ());
                
                Classifier new_classifier = new Classifier (new_classifier_element, classifier);

		Gatherer.invokeInEDT_replacesProceedInCurrThread(
		 "ClassifierManager.AddClassifierTask.run()",
		 Gatherer.ASYNC,			
		 new Runnable() {
		     public void run() {
			 
			 ArgumentConfiguration ac = new ArgumentConfiguration (new_classifier);
			 ac.addOKButtonActionListener (CollectionDesignManager.buildcol_change_listener);
			 if (ac.display ()) {
			     assignClassifier (new_classifier);
			     classifier_list.setSelectedValue (new_classifier, true);
			 }
		     }
		 });
            }
        }
        
        
        /** This listener reacts to changes in the current selection of the classifier combobox. */
        private class ClassifierComboboxListener
        implements ItemListener {
            /** When a user selects a certain classifier, update the tooltip to show the classifier description. */
            public void itemStateChanged (ItemEvent event) {
                if(event.getStateChange () == ItemEvent.SELECTED) {
                    // Retrieve the selected classifier
                    Classifier current_selection = (Classifier) classifier_combobox.getSelectedItem ();
                    // And reset the tooltip.
                    classifier_combobox.setToolTipText (Utility.formatHTMLWidth (current_selection.getDescription (), 40));
                    current_selection = null;
                }
            }
        }
        
        
        /** Listens for double clicks apon the list and react as if the configure button was pushed. */
        private class ClickListener
        extends MouseAdapter {
            /** Called whenever the mouse is clicked over a registered component, we use this to chain through to the configure prompt.
             * @param event A <strong>MouseEvent</strong> containing information about the mouse click.
             */
            public void mouseClicked (MouseEvent event) {
                if(event.getClickCount () == 2 ) {
                    if(!classifier_list.isSelectionEmpty ()) {
                        Classifier classifier = (Classifier) classifier_list.getSelectedValue ();
                        ArgumentConfiguration ac = new ArgumentConfiguration (classifier);
                        ac.addOKButtonActionListener (CollectionDesignManager.buildcol_change_listener);
                        if (ac.display ()) {
                            refresh (classifier);
                        }
                        ac.destroy ();
                        ac = null;
                    }
                }
            }
        }
        
        /** This class listens for actions upon the configure button in the controls, and if detected creates a new ArgumentConfiguration dialog box to allow for configuration.
         */
        private class ConfigureListener
        implements ActionListener {
            /** Any implementation of <i>ActionListener</i> must include this method so that we can be informed when an action has occured on one of our target controls.
             * @param event An <strong>ActionEvent</strong> containing information garnered from the control action.
             * @see org.greenstone.gatherer.cdm.ArgumentConfiguration
             * @see org.greenstone.gatherer.cdm.Classifier
             */
            public void actionPerformed (ActionEvent event) {
                if(!classifier_list.isSelectionEmpty ()) {
                    Classifier classifier = (Classifier) classifier_list.getSelectedValue ();
                    ArgumentConfiguration ac = new ArgumentConfiguration (classifier);
                    ac.addOKButtonActionListener (CollectionDesignManager.buildcol_change_listener);
                    if (ac.display ()) {
                        refresh (classifier);
                    }
                    ac.destroy ();
                    ac = null;
                }
            }
        }
        
        /** listens for changes in the list selection and enables the configure and remove buttons if there is a selection, disables them if there is no selection */
        private class ListListener
        implements ListSelectionListener {
            
            public void valueChanged (ListSelectionEvent e) {
                if (!e.getValueIsAdjusting ()) { // we get two events for one change in list selection - use the false one ( the second one)
                    if (classifier_list.isSelectionEmpty ()) {
                        move_up_button.setEnabled (false);
                        move_down_button.setEnabled (false);
                        configure.setEnabled (false);
                        remove.setEnabled (false);
                    }
                    else {
                        configure.setEnabled (true);
                        remove.setEnabled (true);
                        int selected_index = classifier_list.getSelectedIndex ();
                        move_up_button.setEnabled (selected_index !=0);
                        move_down_button.setEnabled (selected_index != model.getSize ()-1);
                    }
                }
            }
        }
        
        /** Listens for actions apon the move buttons in the manager controls, and if detected calls the <i>moveClassifier()</i> method of the manager with the appropriate details. */
        private class MoveListener
        implements ActionListener {
            /** Any implementation of <i>ActionListener</i> must include this method so that we can be informed when an action has occured on one of our target controls.
             * @param event An <strong>ActionEvent</strong> containing information garnered from the control action.
             */
            public void actionPerformed (ActionEvent event) {
                if(!classifier_list.isSelectionEmpty ()) {
                    Object object = classifier_list.getSelectedValue ();
                    if(object instanceof Classifier) {
                        Classifier classifier = (Classifier) object;
                        if(event.getSource () == move_up_button) {
                            moveClassifier (classifier, true, false);
                        }
                        else if(event.getSource () == move_down_button) {
                            moveClassifier (classifier, false, false);
                        }
                        classifier_list.setSelectedValue (classifier, true);
                    }
                }
            }
        }
        
        /** This class listens for actions upon the remove button in the controls, and if detected calls the <i>removeClassifier()</i> method.
         */
        private class RemoveListener
        implements ActionListener {
            /** Any implementation of <i>ActionListener</i> must include this method so that we can be informed when an action has occured on one of our target controls.
             * @param event An <strong>ActionEvent</strong> containing information garnered from the control action.
             */
            public void actionPerformed (ActionEvent event) {
                if(classifier_list.isSelectionEmpty ()) {
                    remove.setEnabled (false);
                    return;
                }
                int selected_index = classifier_list.getSelectedIndex ();
                Object selected_classifier = classifier_list.getSelectedValue ();
                if (!(selected_classifier instanceof Classifier)) {
                    return; // what else could we have here???
                }
                removeClassifier ((Classifier)selected_classifier);
                
                if (selected_index >= classifier_list.getModel ().getSize ()) {
                    selected_index--;
                }
                if (selected_index >=0) {
                    classifier_list.setSelectedIndex (selected_index);
                } else {
                    // no more classifiers in the list
                    remove.setEnabled (false);
                }
            }
        }
    }
}
