<?php

/**
 * An iterator that acts as a window to its inner-iterator. It does this by
 * providing deque-like functionality (allowing shifting off the front and
 * popping off of the end.)
 *
 * This class has similar functionality to PHP's LimitIterator.
 *
 * @author Peter Goodman
 */

class WindowingIterator extends IteratorIterator implements Countable, SeekableIterator {
    
    protected $_key,
              $_offset,
              $_limit,
              $_it;
    
    /**
     * WindowingIterator(SeekableIterator)
     */
    public function __construct(SeekableIterator $it) {
        
        assert($it instanceof Countable);
        
        $this->_limit = count($it);
        $this->_key = $this->_offset = 0;
        $this->_it = $it;
    }
    
    /**
     * $i->current(void) -> mixed
     *
     * Return the current row.
     */
    public function current() {
        return $this->_it->current();
    }
    
    /**
     * $i->shift(void) -> mixed
     *
     * Shift off and return the first record from the record iterator. If there
     * is no record to shift off this will return NULL.
     */
    public function shift() {
        $this->rewind();

        if(!$this->valid())
            return NULL;
        
        $ret = $this->current();
        
        $this->_offset++;
        
        if($this->_key - $this->_offset <= 0)
            $this->_key++;
        
        return $ret;
    }
    
    /**
     * $i->pop(void) -> mixed
     *
     * Pop off and return the last record from the record iterator. If there
     * are no records left then this will return NULL.
     */
    public function pop() {
        try {
            $this->seek($this->_limit - 1);
            $ret = $this->current();
            
            $this->_limit--;
            $this->rewind();
            
            return $ret;
        
        } catch(OutOfBoundsException $e) {
            return NULL;
        }
    }
    
    /**
     * $i->seek(int $key) -> void
     *
     * Seek to a specific row in the iterator. Rows are indexed from [0, n-1].
     * If the row number supplied is out of bounds, ie: it is below zero or
     * above n-1 then a OutOfBoundsException is thrown.
     */
    public function seek($key) {
        
        // make sure the key is in the right place
        if($key < $this->_offset || $key >= $this->_limit) {
            throw new OutOfBoundsException(
                "Could not access row [{$key}] of record set."
            );
        }
        
        $this->_key = $key;
    }
    
    /**
     * $i->key(void) -> int
     *
     * Return the current row number.
     */
    public function key() {
        return $this->_key;
    }
    
    /**
     * $i->count(void) -> int
     *
     * Return the number of rows viewable through the window.
     */
    public function count() {
        return $this->_limit;
    }
    
    /**
     * $i->rewind(void) -> void
     *
     * Rewind the record iterator to start at row zero.
     */
    public function rewind() {
        if($this->_key > $this->_offset)
            $this->seek($this->_offset);
    }
    
    /**
     * $i->valid(void) -> bool
     *
     * Check if the row in the record iterator represented by the current row
     * id exists. If it doesn't this will return FALSE.
     */
    public function valid() {
        return ($this->_key < $this->_limit) && 
               ($this->_key >= $this->_offset);
    }
    
    /**
     * $i->next(void) -> void
     *
     * Move the internal record pointer to the next record.
     */
    public function next() {
        $this->_key++;
    }
}
