/*
 * Copyright 2008 JBoss Inc
 * 
 * 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.
 *
 * Created on Apr 26, 2008
 */

package org.drools.rule;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

import org.drools.common.InternalWorkingMemory;
import org.drools.common.PropagationContextImpl;
import org.drools.reteoo.RightTuple;
import org.drools.spi.PropagationContext;

/**
 * A length window behavior implementation
 * 
 * @author etirelli
 */
public class SlidingLengthWindow
    implements
    Externalizable,
    Behavior {

    private int size;
    
    public SlidingLengthWindow() {
        this( 0 );
    }

    /**
     * @param size
     */
    public SlidingLengthWindow(final int size) {
        super();
        this.size = size;
    }

    /**
     * @inheritDoc
     *
     * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
     */
    public void readExternal(final ObjectInput in) throws IOException,
                                                  ClassNotFoundException {
        this.size = in.readInt();
    }

    /**
     * @inheritDoc
     *
     * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
     */
    public void writeExternal(final ObjectOutput out) throws IOException {
        out.writeInt( this.size );
        
    }

    public BehaviorType getType() {
        return BehaviorType.LENGTH_WINDOW;
    }

    /**
     * @return the size
     */
    public long getSize() {
        return size;
    }

    /**
     * @param size the size to set
     */
    public void setSize(final int size) {
        this.size = size;
    }

    public Object createContext() {
        return new SlidingLengthWindowContext( this.size );
    }

    /**
     * @inheritDoc
     *
     * @see org.drools.rule.Behavior#assertRightTuple(java.lang.Object, org.drools.reteoo.RightTuple, org.drools.common.InternalWorkingMemory)
     */
    public void assertRightTuple(final Object context,
                                 final RightTuple rightTuple,
                                 final InternalWorkingMemory workingMemory) {
        SlidingLengthWindowContext window = (SlidingLengthWindowContext) context;
        window.pos = (window.pos + 1) % window.rightTuples.length;
        if( window.rightTuples[window.pos] != null ) {
            final RightTuple tuple = window.rightTuples[window.pos];
            // retract previous
            final PropagationContext propagationContext = new PropagationContextImpl( workingMemory.getNextPropagationIdCounter(),
                                                                                      PropagationContext.EXPIRATION,
                                                                                      null,
                                                                                      null,
                                                                                      tuple.getFactHandle() );
            tuple.getRightTupleSink().retractRightTuple( tuple,
                                                         propagationContext,
                                                         workingMemory );
            tuple.unlinkFromRightParent();
            
        }
        window.rightTuples[window.pos] = rightTuple;
    }

    /**
     * @inheritDoc
     *
     * @see org.drools.rule.Behavior#retractRightTuple(java.lang.Object, org.drools.reteoo.RightTuple, org.drools.common.InternalWorkingMemory)
     */
    public void retractRightTuple(final Object context,
                                  final RightTuple rightTuple,
                                  final InternalWorkingMemory workingMemory) {
        SlidingLengthWindowContext window = (SlidingLengthWindowContext) context;
        final int last = ( window.pos == 0 ) ? window.rightTuples.length-1 : window.pos-1;
        // we start the loop on current pos because the most common scenario is to retract the
        // right tuple referenced by the current "pos" position, causing this loop to only execute
        // the first iteration
        for( int i = window.pos; i != last; i = (i+1)%window.rightTuples.length ) {
            if( window.rightTuples[i] == rightTuple ) {
                window.rightTuples[i] = null;
                break;
            }
        }
    }

    public void expireTuples(Object context,
                             InternalWorkingMemory workingMemory) {
        // do nothing
    }
    

    public String toString() {
        return "SlidingLengthWindow( size="+size+" )";
    }

    /**
     * A Context object for length windows
     * 
     * @author etirelli
     */
    private static class SlidingLengthWindowContext
        implements
        Externalizable {
        
        public RightTuple[] rightTuples;
        public int pos = 0;
        
        public SlidingLengthWindowContext( final int size ) {
            this.rightTuples = new RightTuple[size];
        }

        public void readExternal(ObjectInput in) throws IOException,
                                                ClassNotFoundException {
            this.pos = in.readInt();
            this.rightTuples = (RightTuple[]) in.readObject();
            
        }

        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeInt( this.pos );
            out.writeObject( this.rightTuples );
        }
    }

}
