/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.kahadb.index;

import java.util.List;

/**
 * Interface used to selectively visit the entries in a BTree.
 * 
 * @param <Key>
 * @param <Value>
 */
public interface BTreeVisitor<Key,Value> {

    /**
     * Do you want to visit the range of BTree entries between the first and and second key?
     * 
     * @param first if null indicates the range of values before the second key. 
     * @param second if null indicates the range of values after the first key.
     * @return true if you want to visit the values between the first and second key.
     */
    boolean isInterestedInKeysBetween(Key first, Key second);

    /**
     * The keys and values of a BTree leaf node.
     * 
     * @param keys
     * @param values
     */
    void visit(List<Key> keys, List<Value> values);

    public interface Predicate<Key> {
        boolean isInterestedInKeysBetween(Key first, Key second);
        boolean isInterestedInKey(Key key);
    }

    abstract class PredicateVisitor<Key, Value> implements BTreeVisitor<Key, Value>, Predicate<Key> {
		public void visit(List<Key> keys, List<Value> values) {
			for( int i=0; i < keys.size(); i++) {
				Key key = keys.get(i);
				if( isInterestedInKey(key) ) {
					matched(key, values.get(i));
				}
			}
		}

		protected void matched(Key key, Value value) {
        }
    }

    class OrVisitor<Key, Value> extends PredicateVisitor<Key, Value> {
        private final List<Predicate<Key>> conditions;

        public OrVisitor(List<Predicate<Key>> conditions) {
            this.conditions = conditions;
        }

		public boolean isInterestedInKeysBetween(Key first, Key second) {
            for (Predicate<Key> condition : conditions) {
                if( condition.isInterestedInKeysBetween(first, second) ) {
                    return true;
                }
            }
            return false;
		}

        public boolean isInterestedInKey(Key key) {
            for (Predicate<Key> condition : conditions) {
                if( condition.isInterestedInKey(key) ) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            boolean first=true;
            for (Predicate<Key> condition : conditions) {
                if( !first ) {
                    sb.append(" OR ");
                }
                first=false;
                sb.append("(");
                sb.append(condition);
                sb.append(")");
            }
            return sb.toString();
        }
    }

    class AndVisitor<Key, Value> extends PredicateVisitor<Key, Value> {
        private final List<Predicate<Key>> conditions;

        public AndVisitor(List<Predicate<Key>> conditions) {
            this.conditions = conditions;
        }

		public boolean isInterestedInKeysBetween(Key first, Key second) {
            for (Predicate<Key> condition : conditions) {
                if( !condition.isInterestedInKeysBetween(first, second) ) {
                    return false;
                }
            }
            return true;
		}

        public boolean isInterestedInKey(Key key) {
            for (Predicate<Key> condition : conditions) {
                if( !condition.isInterestedInKey(key) ) {
                    return false;
                }
            }
            return true;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            boolean first=true;
            for (Predicate<Key> condition : conditions) {
                if( !first ) {
                    sb.append(" AND ");
                }
                first=false;
                sb.append("(");
                sb.append(condition);
                sb.append(")");
            }
            return sb.toString();
        }
    }

    class BetweenVisitor<Key extends Comparable<Key>, Value> extends PredicateVisitor<Key, Value> {
		private final Key first;
        private final Key last;

        public BetweenVisitor(Key first, Key last) {
			this.first = first;
            this.last = last;
        }

		public boolean isInterestedInKeysBetween(Key first, Key second) {
        	return (second==null || second.compareTo(this.first)>=0)
                   && (first==null || first.compareTo(last)<0);
		}

        public boolean isInterestedInKey(Key key) {
            return key.compareTo(first) >=0 && key.compareTo(last) <0;
        }

        @Override
        public String toString() {
            return first+" <= key < "+last;
        }
    }

    class GTVisitor<Key extends Comparable<Key>, Value> extends PredicateVisitor<Key, Value> {
		final private Key value;

		public GTVisitor(Key value) {
			this.value = value;
		}

		public boolean isInterestedInKeysBetween(Key first, Key second) {
        	return second==null || second.compareTo(value)>0;
		}

        public boolean isInterestedInKey(Key key) {
            return key.compareTo(value)>0;
        }

        @Override
        public String toString() {
            return "key > "+ value;
        }
    }

    class GTEVisitor<Key extends Comparable<Key>, Value> extends PredicateVisitor<Key, Value> {
		final private Key value;

		public GTEVisitor(Key value) {
			this.value = value;
		}

		public boolean isInterestedInKeysBetween(Key first, Key second) {
        	return second==null || second.compareTo(value)>=0;
		}

        public boolean isInterestedInKey(Key key) {
            return key.compareTo(value)>=0;
        }

        @Override
        public String toString() {
            return "key >= "+ value;
        }
    }
    
    class LTVisitor<Key extends Comparable<Key>, Value> extends PredicateVisitor<Key, Value> {
		final private Key value;

		public LTVisitor(Key value) {
			this.value = value;
		}

		public boolean isInterestedInKeysBetween(Key first, Key second) {
        	return first==null || first.compareTo(value)<0;
		}

        public boolean isInterestedInKey(Key key) {
            return key.compareTo(value)<0;
        }

        @Override
        public String toString() {
            return "key < "+ value;
        }
    }
    
    class LTEVisitor<Key extends Comparable<Key>, Value> extends PredicateVisitor<Key, Value> {
		final private Key value;

		public LTEVisitor(Key value) {
			this.value = value;
		}

		public boolean isInterestedInKeysBetween(Key first, Key second) {
        	return first==null || first.compareTo(value)<=0;
		}

        public boolean isInterestedInKey(Key key) {
            return key.compareTo(value)<=0;
        }

        @Override
        public String toString() {
            return "key <= "+ value;
        }
    }
}
