/*
 * $Header: /home/cvspublic/jakarta-slide/src/stores/org/apache/slide/index/lucene/expressions/MergeExpression.java,v 1.4 2004/12/07 17:49:57 luetzkendorf Exp $
 * $Revision: 1.4 $
 * $Date: 2004/12/07 17:49:57 $
 *
 * ====================================================================
 *
 * Copyright 1999-2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.apache.slide.index.lucene.expressions;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;

import org.apache.slide.index.lucene.Index;


/**
 * Implements <code>and</code> and <code>or</code>.
 */
public class MergeExpression extends AbstractLuceneExpression
{
    /**
     * Constructor.
     * @param index The index to be searched.
     * @param and <code>true</code> if AND or <code>false</code> if OR
     * @param expressions list of expressions (must all be derived from 
     *        {@link AbstractLuceneExpression}, i.e. must all be lucene executable)
     */
    public MergeExpression(Index index, boolean and, Collection expressions)
    {
        super(index);
        
        expressions = optimizeRanges(and, expressions);
        
        if (expressions.size() == 1) {
            AbstractLuceneExpression expression = 
                (AbstractLuceneExpression)expressions.iterator().next();
            setQuery(expression.getQuery());
            
        } else {
            BooleanQuery booleanQuery = new BooleanQuery();
            
            for(Iterator i = expressions.iterator(); i.hasNext();) {
                Object e = i.next();
                AbstractLuceneExpression expression = (AbstractLuceneExpression)e;
                Query q = expression.getQuery();
                booleanQuery.add(q, and, false);
            }
            setQuery(booleanQuery);
        }        
    }
    
    private Collection optimizeRanges(boolean and, Collection expressions) {
        Map fields = new HashMap();
        List result = new ArrayList(expressions.size());
        
        // search for comparision operators that work on same field
        
        for (Iterator i = expressions.iterator(); i.hasNext();) {
            Object o = i.next();
            if (o instanceof RangeOperator) {
                RangeOperator op = (RangeOperator)o;
                
                List ops = (List)fields.get(op.getField());
                if (ops == null) {
                    ops = new ArrayList();
                    fields.put(op.getField(), ops);
                }
                ops.add(op);
            } else {
                result.add(o);
            }
        }
        
        for (Iterator i = fields.entrySet().iterator(); i.hasNext();) {
            Map.Entry e = (Map.Entry)i.next();
            List ops = (List)e.getValue();
            
            if (ops.size() == 2) {
                AbstractLuceneExpression expr =  optBetween(
                        (RangeOperator)ops.get(0), 
                        (RangeOperator)ops.get(1), and);
                if (expr != null) {
                    result.add(expr);
                }
            } else {
                result.addAll(ops);
            }
        }
        
        return result;
    }
    
    private AbstractLuceneExpression optBetween(RangeOperator o1, RangeOperator o2, boolean and) {
        if (o1 instanceof GtExpression) {
            if (o2 instanceof LtExpression) {
                if (and) {
                    if (o1.getValue().compareTo(o2.getValue()) <= 0) {
                        //   ------o1>++++++<o2------
                        return new BetweenExpression(this.index, o1.getField(), 
                                o1.getValue(), o2.getValue(), 
                                o1.inclusive(), o2.inclusive(),
                                false);
                    } else {
                        //   ------<o2------o1>------
                        return null;
                    }
                } else {
                    if (o1.getValue().compareTo(o2.getValue()) >= 0) {
                        //   ++++++<o2------o1>++++++
                        return new BetweenExpression(this.index, o1.getField(), 
                                o2.getValue(), o1.getValue(), 
                                o2.inclusive(), o1.inclusive(), 
                                true); // negated
                    } else {
                        //   ++++++o1>+++++<o2+++++++
                        return new IsDefinedExpression(this.index, o1.getField(), false);
                    }
                }
            }   
            if (o2 instanceof GtExpression) {
                if (and) {
                    if (o1.getValue().compareTo(o2.getValue()) <= 0) {
                        // -----o1>-----o2>++++++
                        return (AbstractLuceneExpression)o2;
                    } else {
                        // -----o2>-----o1>++++++
                        return (AbstractLuceneExpression)o1;
                    }
                } else {
                    if (o1.getValue().compareTo(o2.getValue()) <= 0) {
                        // -----o1>+++++o2>++++++
                        return (AbstractLuceneExpression)o1;
                    } else {
                        // -----o2>+++++o1>++++++
                        return (AbstractLuceneExpression)o2;
                    }
                }
            }
            throw new RuntimeException();
        }
        if (o1 instanceof LtExpression) {
            if (o2 instanceof GtExpression) {
                if (and) {
                    if (o1.getValue().compareTo(o2.getValue()) <= 0) {
                        //   ------<o1------o2>------
                        return null;
                    } else {
                        //   ------o2>++++++<o1------
                        return new BetweenExpression(this.index, o1.getField(), 
                                o2.getValue(), o1.getValue(),  
                                o2.inclusive(), o1.inclusive(),
                                false);
                    }
                } else {
                    if (o1.getValue().compareTo(o2.getValue()) >= 0) {
                        //   ++++++o2>+++++<o1++++++
                        return new IsDefinedExpression(this.index, o1.getField(), false);
                    } else {
                        //   ++++++<o1-----o2>+++++++
                        return new BetweenExpression(this.index, o1.getField(), 
                                o1.getValue(), o2.getValue(), 
                                o1.inclusive(), o2.inclusive(), 
                                true); // negated
                    }
                }
            }
            if (o2 instanceof LtExpression) {
                if (and) {
                    if (o1.getValue().compareTo(o2.getValue()) <= 0) {
                        // +++++<o1-----<o2------
                        return (AbstractLuceneExpression)o1;
                    } else {
                        // +++++<o2-----<o1------
                        return (AbstractLuceneExpression)o2;
                    }
                } else {
                    if (o1.getValue().compareTo(o2.getValue()) <= 0) {
                        // +++++<o1+++++<o2------
                        return (AbstractLuceneExpression)o2;
                    } else {
                        // +++++<o2+++++<o1------
                        return (AbstractLuceneExpression)o1;
                    }
                }
            }
            throw new RuntimeException();
        }
        throw new RuntimeException();
    }
}
