package data.filters;

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

import util.*;

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

/**
  * A filter for Catalogs.
  *
  * <p>CatalogFilters can be used to present partial views of a Catalog to parts of your application, e.g.,
  * GUI elements. However, you cannot use a CatalogFilter as a replacement for a 'real' Catalog, e.g., as an
  * item in another Catalog.</p>
  *
  * <p>The actual filter condition is defined by overriding method {@link #match}.</p>
  *
  * @author Steffen Zschaler
  * @version 2.0 19/08/1999
  * @since v2.0
  */
public abstract class CatalogFilter extends Object implements Catalog,
                                                              CatalogChangeListener,
                                                              ListenableCatalog,
                                                              HelpableListener {

  /**
    * The Catalog that is being filtered.
    *
    * @serial
    */
  protected Catalog m_cOrg;

  /**
    * The listeners that listen for events from this Catalog.
    *
    * @serial
    */
  protected ListenerHelper m_lhListeners = new ListenerHelper (this);

  /**
    * Create a new CatalogFilter.
    *
    * @param cOrg the Catalog to be filtered.
    */
  public CatalogFilter (Catalog cOrg) {
    super();

    m_cOrg = cOrg;
  }

  /**
    * Compare the source Catalog to the given object.
    *
    * @override Never
    */
  public int compareTo (Object o) {
    return m_cOrg.compareTo (o);
  }

  /**
    * Detach the current name context from the source Catalog.
    *
    * @override Never
    */
  public NameContext detachNC() {
    return m_cOrg.detachNC();
  }

  /**
    * Attach the given name context to the source Catalog.
    *
    * @override Never
    */
  public NameContext attach (NameContext nc) {
    return m_cOrg.attach (nc);
  }

  /**
    * Get the source Catalog's name.
    *
    * @override Never
    */
  public String getName() {
    return m_cOrg.getName();
  }

  /**
    * Set the source Catalog's name.
    *
    * @override Never
    */
  public void setName (String sName, DataBasket db) throws NameContextException {
    m_cOrg.setName (sName, db);
  }

  /**
    * Get the source Catalog's value.
    *
    * @override Never
    */
  public Value getValue() {
    return m_cOrg.getValue();
  }

  /**
    * Register the listener with the source Catalog.
    *
    * @override Never
    */
  public void addPropertyChangeListener (PropertyChangeListener pcl) {
    m_cOrg.addPropertyChangeListener (pcl);
  }

  /**
    * Un-Register the listener with the source Catalog.
    *
    * @override Never
    */
  public void removePropertyChangeListener (PropertyChangeListener pcl) {
    m_cOrg.removePropertyChangeListener (pcl);
  }

  /**
    * Register the listener with the source Catalog.
    *
    * @override Never
    */
  public void addNameListener (PropertyChangeListener pcl) {
    m_cOrg.addNameListener (pcl);
  }

  /**
    * Un-Register the listener with the source Catalog.
    *
    * @override Never
    */
  public void removeNameListener (PropertyChangeListener pcl) {
    m_cOrg.removeNameListener (pcl);
  }

  /**
    * Register the listener with the source Catalog.
    *
    * @override Never
    */
  public void addValueListener (PropertyChangeListener pcl) {
    m_cOrg.addValueListener (pcl);
  }

  /**
    * Un-Register the listener with the source Catalog.
    *
    * @override Never
    */
  public void removeValueListener (PropertyChangeListener pcl) {
    m_cOrg.removeValueListener (pcl);
  }
  
  /**
    * Get the source Catalog's Catalog.
    *
    * @override Never
    */
  public Catalog getCatalog() {
    return m_cOrg.getCatalog();
  }

  /**
    * Add the given item to the source Catalog.
    *
    * @override Never
    */
  public void add (CatalogItem ci, DataBasket db) {
    m_cOrg.add (ci, db);
  }

  /**
    * Remove the given item from the source Catalog if it is contained in the filtered Catalog.
    *
    * @override Never
    */
  public CatalogItem remove (CatalogItem ci, DataBasket db)
    throws VetoException {
    if (match (ci)) {
      return m_cOrg.remove (ci, db);
    }
    else {
      return null;
    }
  }

  /**
    * Remove the given item from the source Catalog if it is contained in the filtered Catalog.
    *
    * @override Never
    */
  public CatalogItem remove (String sKey, DataBasket db)
    throws VetoException {
    if (get (sKey, db, false) != null) {
      return m_cOrg.remove (sKey, db);
    }
    else {
      return null;
    }
  }

  /**
    * Get the indicated item from the source Catalog if it is contained in the filtered Catalog.
    *
    * @override Never
    */
  public CatalogItem get (String sKey, DataBasket db, boolean fForEdit)
    throws VetoException {
    CatalogItem ci = m_cOrg.get (sKey, db, fForEdit);

    if (match (ci)) {
      return ci;
    }
    else {
      return null;
    }
  }

  /**
    * Check whether the indicated item is contained in the filtered Catalog.
    *
    * @override Never
    */
  public boolean contains (String sKey, DataBasket db) {
    if (m_cOrg.contains (sKey, db)) {
      try {
        CatalogItem ci = get (sKey, db, false);

        return (ci != null);
      }
      catch (VetoException e) {
        return false;
      }
    }
    else {
      return false;
    }
  }

  /**
    * An iterator that returns only items that are contained in the filtered Catalog.
    *
    * @author Steffen Zschaler
    * @version 2.0 19/08/1999
    * @since v2.0
    */
  private class FilteredIterator implements Iterator {
    private Iterator m_i;
    private Object m_oCurrent;

    public FilteredIterator (Iterator i) {
      m_i = i;
    }

    public boolean hasNext() {
      return findNext();
    }

    public Object next() {
      if (!findNext()) {
        throw new NoSuchElementException();
      }

      Object oReturn = m_oCurrent;
      m_oCurrent = null;
      return oReturn;
    }

    public void remove() {
      m_i.remove(); // may have to be more sophisticated !
    }

    /**
      * Find the next item that matches the condition. Helper function.
      *
      * @return true if there was still a next item
      */
    private boolean findNext() {
      if (m_oCurrent != null) {
        return true;
      }

      while (m_i.hasNext()) {
        m_oCurrent = m_i.next();

        if (match ((CatalogItem) m_oCurrent)) {
          return true;
        }
      }

      m_oCurrent = null;
      return false;
    }
  }

  /**
    * Get an iterator of all items that are contained in the filtered Catalog.
    *
    * @override Never
    */
  public Iterator iterator (DataBasket db, boolean fForEdit) {
    return new FilteredIterator (m_cOrg.iterator (db, fForEdit));
  }

  /**
    * Return a set that contains all keys for which a CatalogItem is contained in the filtered Catalog.
    *
    * @override Never
    */
  public Set keySet (final DataBasket db) {
    // return filtered set
    class S extends AbstractSet {
      public Iterator iterator() {
        return new FilteredIterator (m_cOrg.iterator (db, false)) {
          public Object next() {
            CatalogItem ci = (CatalogItem) super.next();
            return ci.getName();
          }

          public void remove() {
            throw new UnsupportedOperationException();
          }
        };
      }

      public int size() {
        return CatalogFilter.this.size (db);
      }
    }

    return new S();
  }

  /**
    * Calculate the size of the filtered Catalog.
    *
    * @override Never
    */
  public int size (DataBasket db) {
    if (m_cOrg.size (db) > 0) {
      int nReturn = 0;

      for (Iterator i = m_cOrg.iterator (db, false); i.hasNext();) {
        if (match ((CatalogItem) i.next())) {
          nReturn++;
        }
      }

      return nReturn;
    }
    else {
      return 0;
    }
  }

  /**
    * Filter condition.
    *
    * @param ci the item to be tested
    *
    * @return true if the given item shall be an item of the filtered Catalog.
    *
    * @override Always
    */
  protected abstract boolean match (CatalogItem ci);

  // CatalogChangeListener interface methods

  /**
    * Translate and propagate the event to all listeners of this Catalog.
    *
    * @override Never
    */
  public void addedCatalogItem (CatalogChangeEvent e) {
    fireCatalogItemAdded (e.getAffectedItem(), e.getBasket());
  }

  /**
    * Translate and propagate the event to all listeners of this Catalog.
    *
    * @override Never
    */
  public void commitedAddCatalogItem (CatalogChangeEvent e) {
    fireCatalogItemAddCommit (e.getAffectedItem(), e.getBasket());
  }

  /**
    * Translate and propagate the event to all listeners of this Catalog.
    *
    * @override Never
    */
  public void rolledbackAddCatalogItem (CatalogChangeEvent e) {
    fireCatalogItemAddRollback (e.getAffectedItem(), e.getBasket());
  }

  /**
    * Translate and propagate the event to all listeners of this Catalog.
    *
    * @override Never
    */
  public void canRemoveCatalogItem (CatalogChangeEvent e) throws VetoException {
    fireCanRemoveCatalogItem (e.getAffectedItem(), e.getBasket());
  }

  /**
    * Translate and propagate the event to all listeners of this Catalog.
    *
    * @override Never
    */
  public void noRemoveCatalogItem (CatalogChangeEvent e) {
    fireNoRemoveCatalogItem (e.getAffectedItem(), e.getBasket());
  }

  /**
    * Translate and propagate the event to all listeners of this Catalog.
    *
    * @override Never
    */
  public void removedCatalogItem (CatalogChangeEvent e) {
    fireCatalogItemRemoved (e.getAffectedItem(), e.getBasket());
  }

  /**
    * Translate and propagate the event to all listeners of this Catalog.
    *
    * @override Never
    */
  public void commitedRemoveCatalogItem (CatalogChangeEvent e) {
    fireCatalogItemRemoveCommit (e.getAffectedItem(), e.getBasket());
  }

  /**
    * Translate and propagate the event to all listeners of this Catalog.
    *
    * @override Never
    */
  public void rolledbackRemoveCatalogItem (CatalogChangeEvent e) {
    fireCatalogItemRemoveRollback (e.getAffectedItem(), e.getBasket());
  }

  /**
    * Translate and propagate the event to all listeners of this Catalog.
    *
    * @override Never
    */
  public void canEditCatalogItem (CatalogChangeEvent e) throws VetoException {
    fireCanEditCatalogItem (e.getAffectedItem(), e.getBasket());
  }

  /**
    * Translate and propagate the event to all listeners of this Catalog.
    *
    * @override Never
    */
  public void noEditCatalogItem (CatalogChangeEvent e) {
    fireNoEditCatalogItem (e.getAffectedItem(), e.getBasket());
  }

  /**
    * Translate and propagate the event to all listeners of this Catalog.
    *
    * @override Never
    */
  public void editingCatalogItem (CatalogChangeEvent e) {
    fireEditingCatalogItem (e.getAffectedItem(), e.getBasket());
  }

  /**
    * Translate and propagate the event to all listeners of this Catalog.
    *
    * @override Never
    */
  public void commitEditCatalogItem (CatalogChangeEvent e) {
    fireCommitEditCatalogItem (e.getAffectedItem(), e.getBasket());
  }

  /**
    * Translate and propagate the event to all listeners of this Catalog.
    *
    * @override Never
    */
  public void rollbackEditCatalogItem (CatalogChangeEvent e) {
    fireRollbackEditCatalogItem (e.getAffectedItem(), e.getBasket());
  }

  // ListenableCatalog interface methods

  /**
    * Add a listener that wishes to receive events when the filtered Catalog changes.
    *
    * @override Never
    */
  public void addCatalogChangeListener (CatalogChangeListener ccl) {
    m_lhListeners.add (CatalogChangeListener.class, ccl);
  }

  /**
    * Remove a listener that received events when the filtered Catalog changed.
    *
    * @override Never
    */
  public void removeCatalogChangeListener (CatalogChangeListener ccl) {
    m_lhListeners.remove (CatalogChangeListener.class, ccl);
  }

  /**
    * Fire the event to all listeners of this Catalog.
    *
    * @override Never
    */
  protected void fireCatalogItemAdded (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();
    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).addedCatalogItem (cce);
      }
    }
  }

  /**
    * Fire the event to all listeners of this Catalog.
    *
    * @override Never
    */
  protected void fireCatalogItemAddCommit (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();
    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).commitedAddCatalogItem (cce);
      }
    }
  }

  /**
    * Fire the event to all listeners of this Catalog.
    *
    * @override Never
    */
  protected void fireCatalogItemAddRollback (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();
    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).rolledbackAddCatalogItem (cce);
      }
    }
  }

  /**
    * Fire the event to all listeners of this Catalog.
    *
    * @override Never
    */
  protected void fireCatalogItemRemoved (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();
    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).removedCatalogItem (cce);
      }
    }
  }

  /**
    * Fire the event to all listeners of this Catalog.
    *
    * @override Never
    */
  protected void fireCatalogItemRemoveCommit (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();
    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).commitedRemoveCatalogItem (cce);
      }
    }
  }

  /**
    * Fire the event to all listeners of this Catalog.
    *
    * @override Never
    */
  protected void fireCatalogItemRemoveRollback (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();
    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).rolledbackRemoveCatalogItem (cce);
      }
    }
  }

  /**
    * Fire the event to all listeners of this Catalog.
    *
    * @override Never
    */
  protected void fireCanRemoveCatalogItem (CatalogItem ci, DataBasket db)
    throws VetoException {
    Object[] temp = m_lhListeners.getListenerList();
    Object[] listeners = new Object[temp.length];
    System.arraycopy (temp, 0, listeners, 0, temp.length);

    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).canRemoveCatalogItem (cce);
      }
    }
  }

  /**
    * Fire the event to all listeners of this Catalog.
    *
    * @override Never
    */
  protected void fireNoRemoveCatalogItem (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();
    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).noRemoveCatalogItem (cce);
      }
    }
  }

  /**
    * Fire the event to all listeners of this Catalog.
    *
    * @override Never
    */
  protected void fireCanEditCatalogItem (CatalogItem ci, DataBasket db)
    throws VetoException {
    Object[] temp = m_lhListeners.getListenerList();
    Object[] listeners = new Object[temp.length];
    System.arraycopy (temp, 0, listeners, 0, temp.length);

    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).canEditCatalogItem (cce);
      }
    }
  }

  /**
    * Fire the event to all listeners of this Catalog.
    *
    * @override Never
    */
  protected void fireNoEditCatalogItem (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();
    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).noEditCatalogItem (cce);
      }
    }
  }

  /**
    * Fire the event to all listeners of this Catalog.
    *
    * @override Never
    */
  protected void fireEditingCatalogItem (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();

    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).editingCatalogItem (cce);
      }
    }
  }

  /**
    * Fire the event to all listeners of this Catalog.
    *
    * @override Never
    */
  protected void fireCommitEditCatalogItem (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();

    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).commitEditCatalogItem (cce);
      }
    }
  }

  /**
    * Fire the event to all listeners of this Catalog.
    *
    * @override Never
    */
  protected void fireRollbackEditCatalogItem (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();

    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).rollbackEditCatalogItem (cce);
      }
    }
  }

  // HelpableListener interface methods
  /**
    * Subscribe as a listener to the source Catalog if that is a {@link ListenableCatalog}.
    *
    * @override Never
    */
  public void subscribe() {
    if (m_cOrg instanceof ListenableCatalog) {
      ((ListenableCatalog) m_cOrg).addCatalogChangeListener (this);
    }
  }

  /**
    * Un-Subscribe as a listener from the source Catalog if that is a {@link ListenableCatalog}.
    *
    * @override Never
    */
  public void unsubscribe() {
    if (m_cOrg instanceof ListenableCatalog) {
      ((ListenableCatalog) m_cOrg).removeCatalogChangeListener (this);
    }
  }

  /**
    * Empty method body.
    *
    * @override Never
    */
  public void updateModel() {}
}