package data.swing;

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

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

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

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

  /**
    * The DataBasket being modelled.
    *
    * @serial
    */
  protected DataBasket m_dbBasket;

  /**
    * The condition specifying the items to be displayed.
    *
    * @serial
    */
  protected DataBasketCondition m_dbcCondition;

  /**
    * A strategy that will group individual DataBasketEntries together for display. If <code>null</code>, no
    * grouping will occur.
    *
    * @serial
    */
  protected DataBasketEntryGrouper m_dbegGrouper;

  /**
    * The Comparator that defines the sorting order of records in the model. It compares
    * {@link DataBasketEntry DataBasketEntries}.
    *
    * @serial
    */
  protected Comparator m_cmpComparator = new SerializableComparator() {
    public int compare (Object o1, Object o2) {
      DataBasketEntry dbe1 = (DataBasketEntry) o1;
      DataBasketEntry dbe2 = (DataBasketEntry) o2;

      int nRet = dbe1.getMainKey().compareTo (dbe2.getMainKey());

      if (nRet != 0) {
        return nRet;
      }

      if ((dbe1.getSecondaryKey() instanceof Comparable) &&
          (dbe2.getSecondaryKey() instanceof Comparable)) {
        return ((Comparable) dbe1.getSecondaryKey()).compareTo (dbe2.getSecondaryKey());
      }

      return 0;
    }
  };

  /**
    * The internal model. A list of the DataBasketEntries.
    *
    * @serial
    */
  protected List m_lEntries;

  /**
    * Create a new DataBasketTableModel.
    *
    * @param db the DataBasket to be modellled.
    * @param dbc a condition specifying the DataBasketEntries to be part of the model.
    * @param dbeg a strategy that will group individual DataBasketEntries together for display. If
    * <code>null</code>, no grouping will occur.
    * @param cmp a Comparator defining the sort order of the records. If <code>null</code>, records are ordered
    * according to the main key of the entries first and of the secondary key second.
    * @param ted a TableEntryDescriptor that can split individual DataBasketEntries into a table's cells.
    */
  public DataBasketTableModel (DataBasket db,
                               DataBasketCondition dbc,
                               DataBasketEntryGrouper dbeg,
                               Comparator cmp,
                               TableEntryDescriptor ted) {
    super (ted);

    m_dbBasket = db;
    m_dbcCondition = dbc;
    m_dbegGrouper = dbeg;

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

    updateModel();

    listenerList = new ListenerHelper (this);
  }

  /**
    * 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 DataBasketEntry} to be displayed at the given index. May return <code>null</code> if
    * there is no record at the indicated position.
    *
    * @override Never
    */
  public Object getRecord (int row) {
    ((ListenerHelper) listenerList).needModelUpdate();

    if ((row > -1) && (row < getRowCount())) {
      return m_lEntries.get (row);
    }
    else {
      return null;
    }
  }

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

    return m_lEntries.size();
  }

  // HelpableListener interface methods

  /**
    * Subscribe as a listener to the model. If the modelled {@link DataBasket} is a
    * {@link ListenableDataBasket}, subscribe as a listener.
    *
    * @override Never
    */
  public void subscribe() {
    if (m_dbBasket instanceof ListenableDataBasket) {
      ((ListenableDataBasket) m_dbBasket).addDataBasketListener (this);
    }
  }

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

  /**
    * Update the internal model based on the modelled {@link DataBasket}.
    *
    * @override Never
    */
  public synchronized void updateModel() {
    List lEntries = new LinkedList();

    for (Iterator i = m_dbBasket.iterator (m_dbcCondition); i.hasNext();) {
      lEntries.add (i.next());
    }

    List lGroupedEntries = null;

    if (m_dbegGrouper != null) {
      lGroupedEntries = new LinkedList();

      while (true) {
        Iterator i = lEntries.iterator();

        if (i.hasNext()) {
          DataBasketEntry dbeGrouped = (DataBasketEntry) i.next();
          i.remove();

          while (i.hasNext()) {
            DataBasketEntry dbe2 = (DataBasketEntry) i.next();

            if (m_dbegGrouper.canGroup (dbeGrouped, dbe2)) {
              i.remove();

              dbeGrouped = m_dbegGrouper.group (dbeGrouped, dbe2);
            }
          }

          lGroupedEntries.add (dbeGrouped);
        }
        else {
          break;
        }
      }
    }
    else {
      lGroupedEntries = lEntries;
    }

    Collections.sort (lGroupedEntries, m_cmpComparator);

    m_lEntries = lGroupedEntries;
  }

  // DataBasketListener 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 addedDBE (DataBasketEvent e) {
    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 removedDBE (DataBasketEvent e) {
    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 dataBasketChanged (DataBasketEvent e) {
    updateModel();
    fireTableDataChanged();
  }
}