/*
 * 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.common;

import org.drools.core.reteoo.Tuple;

public class TupleSetsImpl<T extends Tuple> implements TupleSets<T> {

    private T insertFirst;
    private T deleteFirst;
    private T updateFirst;
    private T normalizedDeleteFirst;

    private int insertSize;

    public TupleSetsImpl() { }

    TupleSetsImpl( T insertFirst, T updateFirst, T deleteFirst, T normalizedDeleteFirst, int insertSize ) {
        this.insertFirst = insertFirst;
        this.updateFirst = updateFirst;
        this.deleteFirst = deleteFirst;
        this.normalizedDeleteFirst = normalizedDeleteFirst;
        this.insertSize = insertSize;
    }

    public int getInsertSize() {
        return insertSize;
    }

    public T getInsertFirst() {
        return this.insertFirst;
    }

    protected void setInsertFirst( T insertFirst ) {
        this.insertFirst = insertFirst;
    }

    public T getDeleteFirst() {
        return this.deleteFirst;
    }

    protected void setDeleteFirst( T deleteFirst ) {
        this.deleteFirst = deleteFirst;
    }

    public T getUpdateFirst() {
        return this.updateFirst;
    }

    protected void setUpdateFirst( T updateFirst ) {
        this.updateFirst = updateFirst;
    }

    public T getNormalizedDeleteFirst() {
        return normalizedDeleteFirst;
    }

    protected void setNormalizedDeleteFirst( T normalizedDeleteFirst ) {
        this.normalizedDeleteFirst = normalizedDeleteFirst;
    }

    public void resetAll() {
        setInsertFirst( null );
        setDeleteFirst( null );
        setUpdateFirst( null );
        setNormalizedDeleteFirst( null );
        insertSize = 0;
    }

    public boolean addInsert(T tuple) {
        if ( getStagedType( tuple ) == Tuple.UPDATE) {
            // do nothing, it's already staged as an update, which means it's already scheduled for eval too.
            return false;
        }

        setStagedType( tuple, Tuple.INSERT );
        if ( insertFirst == null ) {
            insertFirst = tuple;
            insertSize = 1;
            return true;
        }
        setNextTuple( tuple, insertFirst );
        setPreviousTuple( insertFirst, tuple );
        insertFirst = tuple;
        insertSize++;
        return false;
    }

    public boolean addDelete(T tuple) {
        switch ( getStagedType( tuple ) ) {
            // handle clash with already staged entries
            case Tuple.INSERT:
                removeInsert( tuple );
                return deleteFirst == null;
            case Tuple.UPDATE:
                removeUpdate( tuple );
                break;
        }

        setStagedType( tuple, Tuple.DELETE );
        if ( deleteFirst == null ) {
            deleteFirst = tuple;
            return true;
        }
        setNextTuple( tuple, deleteFirst );
        setPreviousTuple( deleteFirst, tuple );
        deleteFirst = tuple;
        return false;
    }

    public boolean addNormalizedDelete(T tuple) {
        setStagedType( tuple, Tuple.NORMALIZED_DELETE );
        if ( normalizedDeleteFirst == null ) {
            normalizedDeleteFirst = tuple;
            return true;
        }
        setNextTuple( tuple, normalizedDeleteFirst );
        setPreviousTuple( normalizedDeleteFirst, tuple );
        normalizedDeleteFirst = tuple;
        return false;
    }

    public boolean addUpdate(T tuple) {
        if ( getStagedType( tuple ) != Tuple.NONE) {
            // do nothing, it's already staged as insert, which means it's already scheduled for eval too.
            return false;
        }

        setStagedType( tuple, Tuple.UPDATE );
        if ( updateFirst == null ) {
            updateFirst = tuple;
            return true;
        }
        setNextTuple( tuple, updateFirst );
        setPreviousTuple( updateFirst, tuple );
        updateFirst = tuple;
        return false;
    }

    public void removeInsert(T tuple) {
        if ( tuple == insertFirst ) {
            T next = getNextTuple( tuple );
            if ( next != null ) {
                setPreviousTuple( next, null );
            }
            setInsertFirst( next );
        } else {
            T next = getNextTuple( tuple );
            T previous = getPreviousTuple( tuple );
            if ( next != null ) {
                setPreviousTuple( next, previous );
            }
            setNextTuple( previous, next );
        }
        tuple.clearStaged();
        insertSize--;
    }

    public void removeDelete(T tuple) {
        if ( tuple == deleteFirst ) {
            T next = getNextTuple( tuple );
            if ( next != null ) {
                setPreviousTuple( next, null );
            }
            deleteFirst = next;
        } else {
            T next = getNextTuple( tuple );
            T previous = getPreviousTuple( tuple );
            if ( next != null ) {
                setPreviousTuple( next, previous );
            }
            setNextTuple( previous, next );

        }
        tuple.clearStaged();
    }

    public void removeUpdate(Tuple tuple) {
        if ( tuple == updateFirst ) {
            T next = getNextTuple( (T) tuple );
            if ( next != null ) {
                setPreviousTuple( next, null );
            }
            updateFirst = next;
        } else {
            T next = getNextTuple( (T) tuple );
            T previous = getPreviousTuple( (T) tuple );
            if ( next != null ) {
                setPreviousTuple( next, previous );
            }
            setNextTuple( previous, next );
        }
        tuple.clearStaged();
    }

    private void addAllInserts(TupleSets<T> tupleSets) {
        if ( tupleSets.getInsertFirst() != null ) {
            if ( insertFirst == null ) {
                setInsertFirst( tupleSets.getInsertFirst() );
                insertSize = tupleSets.getInsertSize();
            } else {
                T current = insertFirst;
                T last = null;
                while ( current != null ) {
                    last = current;
                    current = getNextTuple( current );
                }
                T tuple = tupleSets.getInsertFirst();
                setNextTuple( last, tuple );
                setPreviousTuple( tuple, last );
                insertSize = insertSize + tupleSets.getInsertSize();
            }
            ( (TupleSetsImpl) tupleSets ).setInsertFirst( null );
        }
    }

    private void addAllDeletes(TupleSets<T> tupleSets) {
        if ( tupleSets.getDeleteFirst() != null ) {
            if ( deleteFirst == null ) {
                setDeleteFirst( tupleSets.getDeleteFirst() );
            } else {
                T current = deleteFirst;
                T last = null;
                while ( current != null ) {
                    last = current;
                    current = getNextTuple( current );
                }
                T tuple = tupleSets.getDeleteFirst();
                setNextTuple( last, tuple );
                setPreviousTuple( tuple, last );
            }
            ((TupleSetsImpl) tupleSets).setDeleteFirst( null );
        }
    }

    private void addAllUpdates(TupleSets<T> tupleSets) {
        if ( tupleSets.getUpdateFirst() != null ) {
            if ( updateFirst == null ) {
                setUpdateFirst( tupleSets.getUpdateFirst() );
            } else {
                T current = updateFirst;
                T last = null;
                while ( current != null ) {
                    last = current;
                    current = getNextTuple( current );
                }
                T tuple = tupleSets.getUpdateFirst();
                setNextTuple( last, tuple );
                setPreviousTuple( tuple, last );
            }
            ( (TupleSetsImpl) tupleSets ).setUpdateFirst( null );
        }
    }

    public void addAll(TupleSets<T> source) {
        addAllInserts( source );
        addAllDeletes( source );
        addAllUpdates( source );
    }

    public void addTo(TupleSets<T> target) {
        target.addAll( this );
    }

    @Override
    public TupleSets<T> takeAll() {
        TupleSets<T> clone = new TupleSetsImpl(insertFirst, updateFirst, deleteFirst, normalizedDeleteFirst, insertSize);
        resetAll();
        return clone;
    }

    public void clear() {
        clear( getInsertFirst() );
        clear( getDeleteFirst() );
        clear( getUpdateFirst() );
        clear( getNormalizedDeleteFirst() );
        resetAll();
    }

    private void clear( T tuple ) {
        while ( tuple != null ) {
            T next = getNextTuple( tuple );
            tuple.clearStaged();
            tuple = next;
        }
    }

    @Override
    public boolean isEmpty() {
        return insertFirst == null && deleteFirst == null && updateFirst == null && normalizedDeleteFirst == null;
    }

    @Override
    public String toStringSizes() {
        return "TupleSets[hasInsert=" + (insertFirst != null) + ", hasDelete=" + (deleteFirst != null) + ", hasUpdate=" + (updateFirst != null) + "]";
    }

    public String toString() {
        StringBuilder sbuilder = new StringBuilder();

        sbuilder.append( "Inserted:\n" );
        appendSet( sbuilder, getInsertFirst() );

        sbuilder.append( "Deleted:\n" );
        appendSet( sbuilder, getDeleteFirst() );

        sbuilder.append( "Updated:\n" );
        appendSet( sbuilder, getUpdateFirst() );

        sbuilder.append( "Normalized Deleted:\n" );
        appendSet( sbuilder, getNormalizedDeleteFirst() );

        return sbuilder.toString();
    }

    private void appendSet( StringBuilder sbuilder, Tuple tuple ) {
        for ( ; tuple != null; tuple = getNextTuple( (T) tuple ) ) {
            sbuilder.append( " " ).append( tuple ).append( "\n" );
        }
    }

    protected T getPreviousTuple( T tuple ) {
        return (T) tuple.getStagedPrevious();
    }

    protected void setPreviousTuple( T tuple, T stagedPrevious ) {
        tuple.setStagedPrevious( stagedPrevious );
    }

    protected T getNextTuple( T tuple ) {
        return tuple.getStagedNext();
    }

    protected void setNextTuple( T tuple, T stagedNext ) {
        tuple.setStagedNext( stagedNext );
    }

    protected void setStagedType( T tuple, short type ) {
        tuple.setStagedType( type );
    }

    protected short getStagedType( T tuple ) {
        return tuple.getStagedType();
    }
}
