<?php
/**
* Web Application Component Toolkit
*
* @link http://www.phpwact.org/
*
* @author Wact Development Team
* @link http://www.phpwact.org/team
*
* @copyright Copyright 2006, Jeff Moore
* @license http://opensource.org/licenses/mit-license.php MIT
*/

/**
 * A wrapper around PHP output buffering functions that provides 
 * out of order rendering through callbacks
 */
class Wact_Response_Buffer {

    /**
     * Slices of output
     * @var array
     */
    protected $slices = array();

    /**
     * Holds the position of the beginning of the pending buffer
     * @var array
     */
    protected $stack = array();

    /**
     * Resolve callbacks in reverse order
     * @param int $start Starting position of the slice to resolve
     */
    protected function resolveCallbacks($start = 0) {
        $i = count($this->slices);
        while (--$i >= $start) {
            if (is_array($this->slices[$i])) {
                $callback = $this->slices[$i]['callback'];
                
                // Manipulate the slices array to allow the callback to 
                // use the buffer, such that new slices are inserted into
                // the right place
                $remainder = array_slice($this->slices, $i+1);
                $this->slices = array_slice($this->slices, 0, $i);
                
                // Buffer the callback
                $this->start();
                try {
                    call_user_func($callback);
                    $this->slices[] = $this->get();
                    
                    // make sure we process any new callbacks inserted
                    // by the callback
                    $i = count($this->slices);
                    
                    // Restore the slices
                    $this->slices = array_merge($this->slices, $remainder);
                } catch (Exception $e) {
                    $this->clean();
                    throw $e;
                }
            }
        }
    }
    
    /**
     * Begin a buffer segment
     */
    function start() {
        if (!empty($this->stack)) {
            $this->slices[] = ob_get_clean();
        }
        ob_start();
        array_push($this->stack, count($this->slices));
    }

    /**
     * define a buffer segment that will be calculated later via callback
     * @param callback $callback
     */
    function onOutputDo($callback) {
        // Convert this to use event architecture
        if (empty($this->stack)) {
            throw new Wact_Response_Exception(
                'Cannot register callbacks if start has not been called');
        }
        if (!empty($this->stack)) {
            $this->slices[] = ob_get_clean();
            ob_start();
        }
        $this->slices[] = array('callback' => $callback);
    }

    /**
     * End a named buffer segment and commits the results to the output stream
     */
    function end() {
        if (empty($this->stack)) {
            throw new Wact_Response_Exception(
                'Cannot end if start has not been called');
        }
        
        $start = array_pop($this->stack);

        if (empty($this->stack) && empty($this->slices)) {
            // no slices, no stack, we can just output the buffer
            // as a shortcut
            ob_end_flush();
            return;
        }

        // Record any pending output
        $this->slices[] = ob_get_clean();

        if (!empty($this->stack)) {
            // Continue buffering
            ob_start();
            return;
        }
        $this->resolveCallbacks($start);
        
        // Output all slices
        foreach($this->slices as $slice) {
            echo $slice;
        }
        
        // remove the slices
        $this->slices = array_slice($this->slices, 0, $start);
    }

    /**
     * End and return a buffered segment without outputting it
     * @return string Captured output from buffer
     */
    function get() {
        if (empty($this->stack)) {
            throw new Wact_Response_Exception(
                'Cannot get if start has not been called');
        }
        
        $start = array_pop($this->stack);

        // Record any pending output
        $this->slices[] = ob_get_clean();

        $this->resolveCallbacks($start);

        $slices = array_slice($this->slices, $start, count($this->slices) - $start);

        // Collect all slices
        $result = '';
        foreach($slices as $slice) {
            $result .= $slice;
        }
        
        // remove the slices
        $this->slices = array_slice($this->slices, 0, $start);

        if (!empty($this->stack)) {
            // Continue buffering
            ob_start();
        }
        
        return $result;
    }

    /**
     * Discard an entire buffer segment
     */
    function clean() {
        if (empty($this->stack)) {
            throw new Wact_Response_Exception(
                'Cannot clean if start has not been called');
        }
        ob_end_clean();
        $start = array_pop($this->stack);
        
        $this->slices = array_slice($this->slices, 0, $start);

        if (!empty($this->stack)) {
            // Continue buffering
            ob_start();
        }
    }
    
}

?>