package data.swing;

import java.util.*;
import java.io.*;
import java.beans.*;

import util.*;
import util.swing.*;

import data.*;
import data.events.*;

/**
  * A {@link javax.swing.table.TableModel} that models the contents of a {@link Catalog}.
  *
  * @author Steffen Zschaler
  * @version 2.0 23/08/1999
  * @since v2.0
  */
public class CatalogTableModel extends AbstractTableModel implements HelpableListener,
                                                                     CatalogChangeListener,
                                                                     PropertyChangeListener,
                                                                     Serializable {

  /**
    * The DataBasket used to determine visibility.
    *
    * @serial
    */
  protected DataBasket m_dbBasket;

  /**
    * The Catalog that is being modelled.
    *
    * @serial
    */
  protected Catalog m_cModel;

  /**
    * The Comparator that defines the sorting order of records in the model. It compares
    * {@link CatalogItem CatalogItems}.
    *
    * @serial
    */
  protected Comparator m_cmpComparator = new NaturalComparator();

  /**
    * The internal model. A list of the CatalogItems' keys.
    *
    * @serial
    */
  protected List m_lKeys;

  /**
    * Create a new CatalogTableModel.
    *
    * @param c the Catalog to be modelled. May be {@link data.filters.CatalogFilter filtered}.
    * @param db the DataBasket to be used to determine visibility.
    * @param cmp a Comparator defining the sort order of the records. If <code>null</code>, records are ordered
    * according to the natural ordering of the CatalogItems.
    * @param ted a TableEntryDescriptor that can split individual CatalogItems into a table's cells.
    */
  public CatalogTableModel (Catalog c,
                            DataBasket db,
                            Comparator cmp,
                            TableEntryDescriptor ted) {
    super (ted);

    m_dbBasket = db;
    m_cModel = c;

    if (cmp != null) {
      m_cmpComparator = cmp;
    }

    listenerList = new ListenerHelper (this);

    updateModel();
  }

  /**
    * Get the record at the given index.
    *
    * @param row the index for which to retrieve the record. Element of [0, {@link #getRowCount}).
    * @return the {@link CatalogItem} to be displayed at the given index. May return <code>null</code> if
    * either there is no record at the indicated position or an exception occurs.
    *
    * @override Never
    */
  public Object getRecord (int row) {
    ((ListenerHelper) listenerList).needModelUpdate();

    try {
      if ((row > -1) && (row < getRowCount())) {
        return m_cModel.get ((String) m_lKeys.get (row), m_dbBasket, false);
      }
      else {
        return null;
      }
    }
    catch (VetoException ex) {
      return null;
    }
  }

  /**
    * Get the number of records in this model.
    *
    * @override Never
    */
  public int getRowCount() {
    ((ListenerHelper) listenerList).needModelUpdate();

    return m_lKeys.size();
  }

  // HelpableListener interface methods

  /**
    * Subscribe as a listener to the model. If the modelled {@link Catalog} is a {@link ListenableCatalog},
    * subscribe as a listener.
    *
    * @override Never
    */
  public void subscribe() {
    if (m_cModel instanceof ListenableCatalog) {
      ((ListenableCatalog) m_cModel).addCatalogChangeListener (this);
    }
  }

  /**
    * Un-Subscribe as a listener from the model. If the modelled {@link Catalog} is a {@link ListenableCatalog},
    * un-subscribe as a listener.
    *
    * @override Never
    */
  public void unsubscribe() {
    if (m_cModel instanceof ListenableCatalog) {
      ((ListenableCatalog) m_cModel).removeCatalogChangeListener (this);
    }
  }

  /**
    * Update the internal model based on the modelled {@link Catalog}.
    *
    * @override Never
    */
  public synchronized void updateModel() {
    List lKeys = new LinkedList (m_cModel.keySet (m_dbBasket));
    Collections.sort (lKeys, new Comparator() {
      public int compare (Object o1, Object o2) {
        try {
          return m_cmpComparator.compare (m_cModel.get ((String) o1, m_dbBasket, false),
                                          m_cModel.get ((String) o2, m_dbBasket, false));
        }
        catch (VetoException ex) {
          return 0;
        }
      }
    });

    m_lKeys = lKeys;
  }

  // CatalogChangeListener interface methods

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void addedCatalogItem (CatalogChangeEvent e) {
    if ((e.getBasket() == m_dbBasket) ||
        (e.getBasket() == null)) {
      checkAdd (e.getAffectedItem());
    }
  }

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void commitedAddCatalogItem (CatalogChangeEvent e) {
    checkAdd (e.getAffectedItem());
  }

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void rolledbackAddCatalogItem (CatalogChangeEvent e) {
    if (e.getBasket() == m_dbBasket) {
      checkRemove (e.getAffectedItem());
    }
  }

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void canRemoveCatalogItem (CatalogChangeEvent e) throws VetoException {}

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void noRemoveCatalogItem (CatalogChangeEvent e) {}

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void removedCatalogItem (CatalogChangeEvent e) {
    checkRemove (e.getAffectedItem());
  }

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void commitedRemoveCatalogItem (CatalogChangeEvent e) {}

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void rolledbackRemoveCatalogItem (CatalogChangeEvent e) {
    checkAdd (e.getAffectedItem());
  }

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void canEditCatalogItem (CatalogChangeEvent e) throws VetoException {}

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void noEditCatalogItem (CatalogChangeEvent e) {}

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void editingCatalogItem (CatalogChangeEvent e) {
    if (e.getBasket() != m_dbBasket) {
      checkRemove (e.getAffectedItem());
    }
    else {
      e.getAffectedItem().addPropertyChangeListener (this);
    }
  }

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void commitEditCatalogItem (CatalogChangeEvent e) {
    if (e.getBasket() != m_dbBasket) {
      checkAdd (e.getAffectedItem());
    }
    else {
      e.getAffectedItem().removePropertyChangeListener (this);

      updateModel();
      fireTableDataChanged();
    }
  }

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void rollbackEditCatalogItem (CatalogChangeEvent e) {
    if (e.getBasket() != m_dbBasket) {
      checkAdd (e.getAffectedItem());
    }
    else {
      e.getAffectedItem().removePropertyChangeListener (this);

      updateModel();
      fireTableDataChanged();
    }
  }

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void propertyChange (PropertyChangeEvent e) {
    if (e.getSource() instanceof CatalogItem) {
      checkUpdate ((CatalogItem) e.getSource());
    }
  }

  /**
    * Internal helper method. Check where, if at all, the given CatalogItem has been added with respect to the
    * internal model.
    *
    * @param ci the added CatalogItem
    *
    * @override Never
    */
  protected synchronized void checkAdd (CatalogItem ci) {
    updateModel();

    int nIdx = m_lKeys.indexOf (ci.getName());

    if (nIdx > -1) {
      fireTableRowsInserted (nIdx, nIdx);
    }
  }

  /**
    * Internal helper method. Check from where, if at all, the given CatalogItem has been removed with respect
    * to the internal model.
    *
    * @param ci the removed CatalogItem
    *
    * @override Never
    */
  protected synchronized void checkRemove (CatalogItem ci) {
    int nIdx = m_lKeys.indexOf (ci.getName());

    updateModel();

    if (nIdx > -1) {
      fireTableRowsDeleted (nIdx, nIdx);
    }
  }

  /**
    * Internal helper method. Check for updates in the given CatalogItem.
    *
    * @param ci the updated CatalogItem
    *
    * @override Never
    */
  protected synchronized void checkUpdate (CatalogItem ci) {
    int nIdx1 = m_lKeys.indexOf (ci.getName());

    updateModel();

    int nIdx2 = m_lKeys.indexOf (ci.getName());

    if (nIdx1 == -1) {
      if (nIdx2 > -1) {
        fireTableRowsInserted (nIdx2, nIdx2);
      }
      else {
        return;
      }
    }
    else {
      if (nIdx2 == -1) {
        fireTableRowsDeleted (nIdx1, nIdx1);
      }
      else {
        if (nIdx1 > nIdx2) {
          int nTemp = nIdx2;
          nIdx2 = nIdx1;
          nIdx1 = nIdx2;
        }

        fireTableRowsUpdated (nIdx1, nIdx2);
      }
    }
  }
}