/*
 *  PHEX - The pure-java Gnutella-servent.
 *  Copyright (C) 2001 - 2006 Phex Development Group
 *
 *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 *  Created on 26.01.2006
 *  --- CVS Information ---
 *  $Id: RuleFilteredSearch.java 3639 2006-12-08 17:47:55Z GregorK $
 */
package phex.query;

import java.util.ArrayList;

import phex.common.IntObj;
import phex.download.RemoteFile;
import phex.event.*;
import phex.rules.Rule;

public class RuleFilteredSearch implements SearchDataListener
{
    private Search search;
    
    /**
     * Associated class that is able to hold search results. Access to this
     * should be locked by holding 'this'. 
     */
    private SearchResultHolder displayedSearchResults;
    
    /**
     * Associated class that is able to hold search hidden results. Access to this
     * should be locked by holding 'this'. 
     */
    private SearchResultHolder hiddenSearchResults;
    
    private Rule[] searchFilterRules;
    
    /**
     * All listeners interested in events of this search.
     */
    private ArrayList<SearchDataListener> listenerList = new ArrayList<SearchDataListener>( 2 );
    
    public RuleFilteredSearch( Search search, Rule ruleFilter )
    {
        this( search, new Rule[] { ruleFilter } );
    }
    
    public RuleFilteredSearch( Search search, Rule[] ruleFilters )
    {
        super();
        displayedSearchResults = new SearchResultHolder();
        hiddenSearchResults = new SearchResultHolder();
        this.search = search;
        this.searchFilterRules = ruleFilters;
        search.addSearchChangeListener(this);
    }
    
    public int getResultCount()
    {
        return displayedSearchResults.getQueryHitCount();
    }
    
    public int getHiddenCount()
    {
        return hiddenSearchResults.getQueryHitCount();
    }
    
    /**
     * Delegates progress to inner search
     * @return
     */
    public int getProgress()
    {
        return search.getProgress();
    }
    
    /**
     * Delegates progress to inner search
     * @return
     */
    public IntObj getProgressObj()
    {
        return search.getProgressObj();
    }
    
    public boolean isSearching()
    {
        return search.isSearching();
    }
    
    public void checkForSearchTimeout( long currentTime )
    {
        search.checkForSearchTimeout(currentTime);
    }
    
    public void startSearching()
    {
        search.startSearching();
    }
    
    public void stopSearching()
    {
        search.stopSearching();
    }
    
    private void processRules( RemoteFile[] remoteFiles )
    {
        for ( int i = 0; i < searchFilterRules.length; i++ )
        {
            searchFilterRules[i].process(search, remoteFiles);
        }
        
        ArrayList<RemoteFile> newHitList = new ArrayList<RemoteFile>( remoteFiles.length );
        for ( int j = 0; j < remoteFiles.length; j++ )
        {
            if ( remoteFiles[j].isFilteredRemoved() )
            {
                continue;
            }
            else if( remoteFiles[j].isFilteredHidden() )
            {
                hiddenSearchResults.addQueryHit(remoteFiles[j]);
            }
            else
            {
                displayedSearchResults.addQueryHit(remoteFiles[j]);
                newHitList.add(remoteFiles[j]);
            }
        }
        // if something was added...
        if ( newHitList.size() > 0 )
        {
            RemoteFile[] newHits = new RemoteFile[ newHitList.size() ];
            newHitList.toArray( newHits );
            fireSearchHitsAdded( newHits );
        }
    }

    public void searchDataChanged( SearchDataEvent event )
    {
        int type = event.getType();
        switch ( type )
        {
        case SearchDataEvent.SEARCH_HITS_ADDED:
            processRules( event.getSearchData() );
            break;
        
        default:
            // all other events are simply forwarded..
            // TODO1 verify if we need to change the source that is set to the 
            // original search currently.
            fireSearchChangeEvent(event);
            break;
        }
    }
    
    
    ///////////////////// START event handling methods ////////////////////////

    public void addSearchDataListener( SearchDataListener listener )
    {
        listenerList.add( listener );
    }

    public void removeSearchDataListener( SearchDataListener listener )
    {
        listenerList.remove( listener );
    }

    protected void fireSearchStarted()
    {
        SearchDataEvent searchChangeEvent =
            new SearchDataEvent( this, SearchDataEvent.SEARCH_STARTED );
        fireSearchChangeEvent( searchChangeEvent );
    }

    protected void fireSearchStoped()
    {
        SearchDataEvent searchChangeEvent =
            new SearchDataEvent( this, SearchDataEvent.SEARCH_STOPED );
        fireSearchChangeEvent( searchChangeEvent );
    }

    protected void fireSearchFiltered()
    {
        SearchDataEvent searchChangeEvent =
            new SearchDataEvent( this, SearchDataEvent.SEARCH_FILTERED );
        fireSearchChangeEvent( searchChangeEvent );
    }
    
    public void fireSearchChanged()
    {
        SearchDataEvent searchChangeEvent =
            new SearchDataEvent( this, SearchDataEvent.SEARCH_CHANGED );
        fireSearchChangeEvent( searchChangeEvent );
    }

    protected void fireSearchHitsAdded( RemoteFile[] newHits )
    {
        SearchDataEvent searchChangeEvent = new SearchDataEvent( this,
            SearchDataEvent.SEARCH_HITS_ADDED, newHits );
        fireSearchChangeEvent( searchChangeEvent );        
    }

    private void fireSearchChangeEvent( final SearchDataEvent searchChangeEvent )
    {
        // invoke update in event dispatcher
        AsynchronousDispatcher.invokeLater(
        new Runnable()
        {
            public void run()
            {
                Object[] listeners = listenerList.toArray();
                SearchDataListener listener;

                // Process the listeners last to first, notifying
                // those that are interested in this event
                for ( int i = listeners.length - 1; i >= 0; i-- )
                {
                    if ( listeners[ i ] instanceof SearchDataListener )
                    {
                        listener = (SearchDataListener)listeners[ i ];
                        listener.searchDataChanged( searchChangeEvent );
                    }
                    
                }
            }
        });
    }
    
    ///////////////////// END event handling methods ////////////////////////
}