package phex.share;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import phex.common.URN;
import phex.common.collections.IntSet;
import phex.common.collections.IntSet.IntSetIterator;
import phex.connection.NetworkManager;
import phex.msg.QueryMsg;
import phex.prefs.core.LibraryPrefs;
import phex.upload.UploadManager;
import phex.utils.NLogger;
import phex.utils.StringUtils;

public class QueryResultSearchEngine
{
    private static final String INDEX_QUERY_STRING = "    ";
    
    private SharedFilesService sharedFilesService;
    
    public QueryResultSearchEngine( SharedFilesService sharedFilesService )
    {
        this.sharedFilesService = sharedFilesService;
    }
    
    /**
     * Search the sharefile database and get groups of sharefiles.
     * It returns empty results if the query source and the local host
     * are both firewalled. 
     * @param queryMsg the query
     * @return the found results.
     */
    public List<ShareFile> handleQuery( QueryMsg queryMsg )
    {
        // Perform search on my list.
        
        // Only send results if we support the possible query feature.
        if ( queryMsg.getFeatureQuerySelector() > QueryMsg.WHAT_IS_NEW_FEATURE_QUERY_SELECTOR )
        {
            return Collections.emptyList();
        }

        // If the query source and the local host are both firewalled, return no results
        // as per http://groups.yahoo.com/group/the_gdf/files/Proposals/MinSpeed.html
        if ( queryMsg.isRequesterFirewalled()
            && !NetworkManager.getInstance().hasConnectedIncoming() )
        {
            return Collections.emptyList();
        }
        // if all upload slots are filled dont return any search results.
        // This holds away unnecessary connection attempts.
        if ( UploadManager.getInstance().isHostBusy() )
        {
            return Collections.emptyList();
        }
        
        if ( queryMsg.isWhatsNewQuery() )
        {// we handle whats new queries special and return the 3 newest files
            return sharedFilesService.getNewestFiles( 3 );
        }

        String searchStr = queryMsg.getSearchString();
        if ( searchStr.equals( INDEX_QUERY_STRING ) )
        {
            NLogger.debug( ShareManager.class,
                "Index query detected." );
            return sharedFilesService.getSharedFiles();
        }

        // first check for URN query...
        List<ShareFile> urnMatches = null;
        URN[] urns = queryMsg.getQueryURNs();
        if ( urns.length > 0 )
        {
            urnMatches = sharedFilesService.getFilesByURNs( urns );
            if ( urnMatches.size() == urns.length )
            {// we found all requested files by URN.
                // return results and be happy that we are already finished.
                return provideResultData( urnMatches, null );
            }
        }
        
        // if there are no urns or not all urns have a hit check for 
        // keyword matches...
        IntSet keyWordMatches = handleKeywordSearch( searchStr );
        
        return provideResultData( urnMatches, keyWordMatches );
    }
    
    /**
     * Performs the keyword query on the search string.
     * @param searchStr the search string
     * @return an IntSet containing the result file index positions.
     */
    private IntSet handleKeywordSearch( String searchStr )
    {
        IntSet searchMatches = null;
        
        int searchStrLength = searchStr.length();
        int startPos = 0;
        int endPos;
        Iterator<IntSet> indexIterator;
        while ( startPos < searchStrLength ) 
        {
            if ( StringUtils.isFileDelimiter( searchStr.charAt( startPos ) ) ) 
            {
                startPos ++;
                continue;
            }
            for ( endPos = startPos + 1; endPos < searchStrLength; endPos ++ ) 
            {
                if ( StringUtils.isFileDelimiter( searchStr.charAt( endPos ) ) )
                {
                    break;
                }
            }
            
            // search for prefixed keyword using word delimiters.
            indexIterator = sharedFilesService.getIndexIteratorForPrefixTerm( 
                searchStr, startPos, endPos );
            if ( indexIterator.hasNext() )
            {
                IntSet keywordMatches = null;
                while ( indexIterator.hasNext() ) 
                {                
                    IntSet match = indexIterator.next();
                    if ( keywordMatches == null ) 
                    {
                        if ( startPos == 0 && endPos == searchStrLength && !indexIterator.hasNext() )
                        {
                            return match;
                        }
                        keywordMatches = new IntSet();
                    }
                    keywordMatches.addAll( match );
                }

                if ( searchMatches == null )
                {
                    searchMatches = keywordMatches;
                }
                else
                {
                    searchMatches.retainAll( keywordMatches );
                    if ( searchMatches.size() == 0 )
                    {
                        // no match we can abort the keyword search
                        return null;
                    }
                }
            } 
            else 
            {
                // no match we can abort the complete keyword search
                return null;
            }
            startPos = endPos;
        }
        
        return searchMatches;
    }
    
    private List<ShareFile> provideResultData( List<ShareFile> urnMatches, 
        IntSet keywordMatches )
    {
        int count = 0;
        if ( urnMatches != null )
        {
            count += urnMatches.size();
        }
        if ( keywordMatches != null )
        {
            count += keywordMatches.size();
        }
        if ( count == 0 )
        {
            return Collections.emptyList();
        }
        
        List<ShareFile> resultList = new ArrayList<ShareFile>();
        final int maxResults = LibraryPrefs.MaxResultsPerQuery.get().intValue();
        int resultListSize = 0;
        if ( urnMatches != null && urnMatches.size() > 0 )
        {
            for ( ShareFile shareFile : urnMatches )
            {
                // increment search count for files in list
                shareFile.incSearchCount();
                resultList.add( shareFile );
                resultListSize ++;
                if ( resultListSize >= maxResults )
                {
                    break;
                }
            }
        }
        if ( keywordMatches != null && keywordMatches.size() > 0 
          && resultListSize < maxResults)
        {
            IntSetIterator iterator = keywordMatches.iterator();
            while( iterator.hasNext() )
            {
                int index = iterator.next();
                ShareFile shareFile = sharedFilesService.getFileByIndex( index );
                if ( shareFile != null )
                {
                    // increment search count for files in list
                    shareFile.incSearchCount();
                    resultList.add( shareFile );
                    resultListSize ++;
                    if ( resultListSize >= maxResults )
                    {
                        break;
                    }
                }
            }
        }
        
        assert( resultList.size() <= maxResults );
                
        return resultList;
    }
        

//    private ArrayList<ShareFile> handleOldKeywordSearch( String searchStr )
//    {
//        // if there are no urns or not all urns have a hit check for string query...
//        StringTokenizer tokenizer = new StringTokenizer( searchStr );
//        ArrayList<String> tokenList = new ArrayList<String>( Math.min( 10,
//            tokenizer.countTokens() ) );
//        String term;
//        // Build search term, max up to 10 terms.
//        while (tokenList.size() < 10 && tokenizer.hasMoreElements())
//        {
//            term = tokenizer.nextToken();
//            // ignore terms with less then 2 char length
//            if ( term.length() >= DynamicQueryConstants.MIN_SEARCH_TERM_LENGTH )
//            {
//                tokenList.add( term.toLowerCase() );
//            }
//        }
//
//        if ( tokenList.size() == 0 )
//        {// no string search to do
//            return null;
//        }
//
//        ShareFile[] shareFiles = sharedFilesService.getSharedFiles();
//        SearchEngine searchEngine = new SearchEngine();
//
//        // searches through the files for each search term. Drops all files that
//        // dont match a search term from the possible result list.
//
//        //long start1 = System.currentTimeMillis();
//        // all files are possible results
//        ArrayList<ShareFile> leftFiles = new ArrayList<ShareFile>( Arrays.asList( shareFiles ) );
//
//        // go through each term
//        for (int i = 0; i < tokenList.size() && leftFiles.size() > 0; i++)
//        {
//            searchEngine.setPattern( tokenList.get( i ), true );
//
//            // go through each left file in the files array
//            for (int j = leftFiles.size() - 1; j >= 0; j--)
//            {
//                ShareFile shareFile = leftFiles.get( j );
//                // dont share files without calculated urn..
//                if ( shareFile.getURN() == null )
//                {
//                    leftFiles.remove( j );
//                    continue;
//                }
//                searchEngine.setText( shareFile.getSearchCompareTerm(), true );
//                if ( !searchEngine.match() )
//                {
//                    // a term dosn't match remove possible result.
//                    leftFiles.remove( j );
//                }
//            }
//        }
//        
//        return leftFiles;
//    }

}
