/*
 * Copyright (2008) Schibsted ASA
 * This file is part of SESAT.
 *
 *   SESAT is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   SESAT 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 Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with SESAT.  If not, see <http://www.gnu.org/licenses/>.
 */

package no.sesat.search.run.handler;

import java.util.List;

import no.sesat.search.datamodel.DataModel;
import no.sesat.search.datamodel.search.SearchDataObject;
import no.sesat.search.result.FacetedSearchResult;
import no.sesat.search.result.FacetedSearchResultImpl;
import no.sesat.search.result.Modifier;
import no.sesat.search.result.ResultItem;
import no.sesat.search.result.ResultList;

import org.apache.log4j.Logger;



/**
 * The handler is responsible for merging (or copying over) the results together.
 *
 * @version $Id: FederatorRunHandler.java 7225 2009-04-09 00:32:20Z ssmiweve $
 *
 */
public final class FederatorRunHandler implements RunHandler{

    private static final Logger log = Logger.getLogger(FederatorRunHandler.class);
    private final FederatorRunHandlerConfig config;


    public FederatorRunHandler(final RunHandlerConfig rhc) {
        config = (FederatorRunHandlerConfig) rhc;
    }


    public void handleRunningQuery(final Context context) {

        final DataModel datamodel = context.getDataModel();

        final SearchDataObject commandTo = datamodel.getSearch(config.getTo());
        if(commandTo==null) {
        	log.error("Cannot find search command "+config.getTo());
        	return;
        }
        ResultList<ResultItem> resultTo = commandTo.getResults();
        
        switch(config.getBlend()){
            case SEQUEL:

            for(String commandFrom : config.getFrom()){
                final SearchDataObject command = datamodel.getSearch(commandFrom);
                if(command==null)
                	continue;
                
                // insert results
                // TODO - this affects paging, but we ignore that here
                ResultList<ResultItem> resultFrom = command.getResults();
                for(int i = 0 ; i < config.getInsertCount(); ++i){
                    if(i < resultFrom.getResults().size()){
                        final ResultItem result = resultFrom.getResults().get(i);
                        insertResult(result, resultTo, config.getInsertPosition());
                    }
                }
                
                // combine result counts
                resultTo.setHitCount(resultTo.getHitCount()+resultFrom.getHitCount());
                
                // merge navigators
                log.info("resultTo is instanceof "+resultTo.getClass().getName()+" and "+(resultTo instanceof FacetedSearchResultImpl));
                log.info("resultFrom is instanceof "+resultFrom.getClass().getName());
                if(resultTo instanceof FacetedSearchResult && resultFrom instanceof FacetedSearchResult) {
                	log.info("Merging navigators from "+commandFrom+" to "+config.getTo());
                	FacetedSearchResult<ResultItem> fResultFrom = (FacetedSearchResult<ResultItem>)resultFrom;
                	FacetedSearchResult<ResultItem> fResultTo = (FacetedSearchResult<ResultItem>)resultTo;
                	for(String navKey : fResultFrom.getNavigatorKeys()) {
                		log.info("Processing modifiers for navKey "+navKey);
                		for(Modifier modFrom : fResultFrom.getModifiers(navKey)) {
                			//TODO - merging modifiers should be part of the Modifier class?
                			//TODO - current merge ignores the fact that they could be different Modifier decendents
            				Modifier modTo = fResultTo.getModifier(navKey, modFrom.getName());
            				log.info("Processing modifier "+modFrom.getName()+", "+modTo==null ? " not on to side" : " already on to side");
                			if(modTo!=null) {
                				//merge here, as FacetedSearchResultImpl just appends modifiers to list even if they 
                				//already exist (modifier name is only retrieval key, so it should be unique)
                				modTo.addCount(modFrom.getCount());
                			}else {
                				//just add
                				fResultTo.addModifier(navKey, modFrom);
                			}
                		}
                	}
                }
            }
            break;

            default:
                throw new UnsupportedOperationException("Not yet implemented");
        }
    }

    // does nothing if insertPosition is greater than size of results
    private void insertResult(
            final ResultItem result,
            final ResultList<ResultItem> results,
            final int insertPosition) {

        final List<ResultItem> original = results.getResults();

        //TODO - why not just change the ResultList interface to allow direct insertion at a position and
        //get rid of the need to implement this logic?
        if( insertPosition < original.size() && insertPosition >= 0 ){

            // duplicate the last result
            results.addResult(original.get(original.size()-1));

            // now shuffle back everything between insertPosition and the last result
            for(int i = results.getResults().size()-2; i > insertPosition; --i){
                // double check i exists in original list.
                if( i < original.size() ){
                    results.replaceResult(original.get(i), original.get(i-1));
                }
            }

            // now add the new result in
            results.replaceResult(original.get(insertPosition), result);
        }
        if( insertPosition == original.size())
        	results.addResult(result);

    }
}
