/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 *      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.drools.core.util.index;

import java.io.Serializable;

import org.drools.core.common.InternalFactHandle;
import org.drools.core.reteoo.TupleMemory;
import org.drools.core.reteoo.Tuple;
import org.drools.core.util.Entry;
import org.drools.core.util.FastIterator;
import org.drools.core.util.Iterator;
import org.drools.core.util.LinkedList;

public class TupleList<C> implements TupleMemory, Entry<TupleList<C>>, Serializable {

    public static final long       serialVersionUID = 510l;

    private TupleList<C>           next;

    private Tuple                  first;
    private Tuple                  last;

    private TupleHashTableIterator iterator;

    private int                    size;

    private C                      context;

    public TupleList() {
    }

    public TupleList(C c) {
        this.context = c;
    }

    public TupleList( Tuple first, Tuple last, int size ) {
        this.first = first;
        this.last = last;
        this.size = size;
    }

    public C getContext() {
        return context;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public Tuple getFirst(Tuple tuple) {
        return this.first;
    }
    
    public Tuple getFirst() {
        return this.first;
    }
    
    public Tuple getLast() {
        return this.last;
    }
    
    public void clear() {
        this.first = null;
        this.last = null;
        size = 0;
    }    
    
    public void removeAdd(Tuple tuple) {
        if (this.last == tuple) {
            return;
        }

        Tuple previous = tuple.getPrevious();
        Tuple next = tuple.getNext();
        if (previous == null) {
            next.setPrevious( null );
            this.first = next;
        } else {
            previous.setNext( next );
            next.setPrevious( previous );
        }

        this.last.setNext( tuple );
        tuple.setPrevious( this.last );
        tuple.setNext( null );
        this.last = tuple;
    }

    public void add(final Tuple tuple) {
        if ( this.last != null ) {
            this.last.setNext( tuple );
            tuple.setPrevious( this.last );
            this.last = tuple;
        } else {
            this.first = tuple;
            this.last = tuple;
        }
        tuple.setMemory( this );
        this.size++;

    }

    public void remove(final Tuple tuple) {
        Tuple previous = tuple.getPrevious();
        Tuple next = tuple.getNext();

        if ( previous != null && next != null ) {
            // remove from middle
            previous.setNext( next );
            next.setPrevious( previous );
        } else if ( next != null ) {
            // remove from first
            this.first = next;
            next.setPrevious( null );
        } else if ( previous != null ) {
            // remove from end
            this.last = previous;
            previous.setNext( null );
        } else {
            // remove everything
            this.last = null;
            this.first = null;
        }        
        tuple.clear();
        this.size--;
    }

    public Tuple removeFirst() {
        Tuple tuple = this.first;
        if ( this.last == tuple ) {
            this.last = null;
            this.first = null;
        }  else {
            this.first = tuple.getNext();
            if ( this.first != null ) {
                this.first.setPrevious(null);
            }
        }
        tuple.clear();
        this.size--;
        return tuple;
    }

    public Tuple get(final Tuple tuple) {
        Tuple current = this.first;
        while ( current != null ) {
            if ( tuple.equals( current ) ) {
                return current;
            }
            current = current.getNext();
        }
        return null;
    }

    public Tuple get(final InternalFactHandle handle) {
        Tuple current = this.first;
        while ( current != null ) {
            if ( handle == current.getFactHandle() ) {
                return current;
            }
            current = current.getNext();
        }
        return null;
    }

    public int size() {
        return this.size;
    }

    public Tuple[] toArray() {
        Tuple[] tuples = new Tuple[this.size];

        Tuple current = first;
        for ( int i = 0; i < this.size; i++ ) {
            tuples[i] = current;
            current = current.getNext();
        }

        return tuples;
    }

    @Override
    public IndexType getIndexType() {
        return TupleMemory.IndexType.NONE;
    }

    public FastIterator<Tuple> fastIterator() {
        return LinkedList.fastIterator; // contains no state, so ok to be static
    }
    
    public FastIterator<Tuple> fullFastIterator() {
        return LinkedList.fastIterator; // contains no state, so ok to be static
    }
    

    public FastIterator<Tuple> fullFastIterator(Tuple tuple) {
        return LinkedList.fastIterator; // contains no state, so ok to be static
    }    

    public Iterator<Tuple> iterator() {
        if ( this.iterator == null ) {
            this.iterator = new TupleHashTableIterator();
        }
        this.iterator.reset( this.first );
        return this.iterator;
    }

    public static class TupleHashTableIterator
        implements
        Iterator<Tuple> {
        private Tuple current;

        public void reset(Tuple first) {
            this.current = first;
        }

        public Tuple next() {
            if ( this.current != null ) {
                Tuple returnValue = this.current;
                this.current = current.getNext();
                return returnValue;
            } else {
                return null;
            }
        }

        public void remove() {
            // do nothing
        }
    }

    public boolean isIndexed() {
        return false;
    }

    public TupleList getNext() {
        return this.next;
    }

    public void setNext(final TupleList next) {
        this.next = next;
    }

    public String toString() {
        StringBuilder builder = new StringBuilder();
        Iterator it = iterator();
        for ( Tuple tuple = (Tuple) it.next(); tuple != null; tuple = (Tuple) it.next() ) {
            builder.append(tuple).append("\n");
        }

        return builder.toString();
    }

    protected void copyStateInto(TupleList other) {
        other.next = next;
        other.first = first;
        other.context = context;
        other.last = last;
        other.iterator = iterator;
        other.size = size;

        for ( Tuple current = first; current != null; current = current.getNext() ) {
            current.setMemory(other);
        }
    }
}
