package org.drools.core.util.index;

import org.drools.common.InternalFactHandle;
import org.drools.core.util.AbstractHashTable.FieldIndex;
import org.drools.core.util.Entry;
import org.drools.core.util.FastIterator;
import org.drools.core.util.Iterator;
import org.drools.core.util.RightTupleRBTree;
import org.drools.core.util.RightTupleRBTree.Boundary;
import org.drools.core.util.RightTupleRBTree.Node;
import org.drools.core.util.index.IndexUtil.ConstraintType;
import org.drools.reteoo.LeftTuple;
import org.drools.reteoo.RightTuple;
import org.drools.reteoo.RightTupleMemory;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.List;

public class RightTupleIndexRBTree implements RightTupleMemory, Externalizable {

    private RightTupleRBTree<Comparable<Comparable>> tree;

    private FieldIndex index;
    private ConstraintType constraintType;

    private int size;

    public RightTupleIndexRBTree() {
        // constructor for serialisation
    }

    public RightTupleIndexRBTree(ConstraintType constraintType, FieldIndex index) {
        this.index = index;
        this.constraintType = constraintType;
        tree = new RightTupleRBTree<Comparable<Comparable>>();
    }

    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject( tree );
        out.writeObject( index );
        out.writeObject( constraintType );
        out.writeInt(size);
    }

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        tree = (RightTupleRBTree<Comparable<Comparable>>) in.readObject();
        index = (FieldIndex) in.readObject();
        constraintType = (ConstraintType) in.readObject();
        size = in.readInt();
    }

    public void add(RightTuple tuple) {
        Comparable key = getIndexedValue(tuple);
        RightTupleList list = tree.insert(key);
        list.add(tuple);
        size++;
    }

    public void remove(RightTuple tuple) {
        RightTupleList list = tuple.getMemory();
        list.remove(tuple);
        if (list.size() == 0) {
            tree.delete(((RightTupleRBTree.Node<Comparable<Comparable>>)list).key);
        }
        size--;
    }

    public void removeAdd(RightTuple tuple) {
        remove(tuple);
        add(tuple);
    }

    public boolean isIndexed() {
        return true;
    }

    public int size() {
        return size;
    }

    public Entry[] toArray() {
        FastIterator it = tree.fastIterator();
        if (it == null) {
            return new Entry[0];
        }

        List<Comparable> toBeRemoved = new ArrayList<Comparable>();
        List<RightTuple> result = new ArrayList<RightTuple>();

        RightTupleList list = null;
        while ( (list = (RightTupleList) it.next( list )) != null ) {
            RightTuple entry = list.getFirst();
            while (entry != null) {
                result.add(entry);
                entry = (RightTuple) entry.getNext();
            }
        }

        return result.toArray(new LeftTuple[result.size()]);
    }

    public RightTuple getFirst(LeftTuple tuple, InternalFactHandle factHandle, FastIterator rightTupleIterator) {
        Comparable key = getIndexedValue(tuple);
        return getNext(key, true);
    }

    public Iterator iterator() {
        RightTupleList list = tree.first();
        RightTuple firstTuple = list != null ? list.first : null;
        return new FastIterator.IteratorAdapter(fastIterator(), firstTuple);
    }

    public boolean contains(RightTuple tuple) {
        Comparable key = getIndexedValue(tuple);
        return tree.lookup(key) != null;
    }

    public FastIterator fastIterator() {
        return new RightTupleFastIterator();
    }

    public FastIterator fullFastIterator() {
        return new RightTupleFastIterator();
    }

    public FastIterator fullFastIterator(RightTuple tuple) {
        FastIterator fastIterator = fullFastIterator();
        Comparable key = getIndexedValue(tuple);
        fastIterator.next(getNext(key, true));
        return fastIterator;
    }

    public IndexType getIndexType() {
        return IndexType.COMPARISON;
    }

    private Comparable getIndexedValue(LeftTuple leftTuple) {
        return (Comparable) index.getDeclaration().getExtractor().getValue(leftTuple.get(index.getDeclaration()).getObject());
    }

    private Comparable getIndexedValue(RightTuple rightTuple) {
        return (Comparable) index.getExtractor().getValue( rightTuple.getFactHandle().getObject() );
    }

    private RightTuple getNext(Comparable key, boolean first) {
        Node<Comparable<Comparable>> firstNode;
        switch (constraintType) {
            case LESS_THAN:
                //firstNode = tree.findNearestNode(key, false, Boundary.UPPER);
                firstNode = tree.findNearestNode(key, false, first ? Boundary.UPPER : Boundary.LOWER);
                break;
            case LESS_OR_EQUAL:
                firstNode = tree.findNearestNode(key, first, Boundary.UPPER);
                break;
            case GREATER_THAN:
                firstNode = tree.findNearestNode(key, false, Boundary.LOWER);
                break;
            case GREATER_OR_EQUAL:
                firstNode = tree.findNearestNode(key, first, Boundary.LOWER);
                break;
            default:
                throw new UnsupportedOperationException("Cannot call getNext constraint of type: " + constraintType);
        }
        return firstNode == null ? null : firstNode.getFirst();
    }

    public class RightTupleFastIterator implements FastIterator {
        public Entry next(Entry object) {
            if (object == null) {
                Node<Comparable<Comparable>> firstNode = tree.first();
                return firstNode == null ? null : firstNode.getFirst();
            }
            RightTuple rightTuple = (RightTuple) object;
            RightTuple next = (RightTuple) rightTuple.getNext();
            if (next != null) {
                return next;
            }
            Comparable key = getIndexedValue(rightTuple);
            return getNext(key, false);
        }

        public boolean isFullIterator() {
            return false;
        }
    }  
}
