<?php
/**
 * FIT Fixture
 * 
 * $Id$
 * 
 * @author gERD Schaufelberger <gerd@php-tools.net>
 * @package FIT
 * @subpackage Parser
 * @license LGPL http://www.gnu.org/copyleft/lesser.html
 */
 
/** 
 * path to user's fixtures
 */
if( !defined( 'TESTING_FIT_FIXTURE_DIR' ) ) {
    define( 'TESTING_FIT_FIXTURE_DIR', '.' );
}

/**
 * load class TypeFilter
 */
if( !class_exists( 'Testing_FIT_TypeFilter' ) ) {
    include 'Testing/FIT/TypeFilter.php';
}

/**
 * FIT Fixture base class
 * 
 * @version 0.1.1
 * @package FIT
 * @subpackage Fixture
 */
class Testing_FIT_Fixture
{
   /**
    * column bindings
    * @var object
    */
    protected $_columnBindings;
    
   /**
    * column filter
    * @var array
    */
    protected $_columnFilter    = array();
    
   /**
    * default filter
    * @var object
    */
    protected $_defaultFilter    = null;
    
   /**
    * type dictionary to figure out which filter to be used
    * @var array
    */
    protected $_typeDictionary = array();

   /**
    * constructor
    * 
    */
    public function __construct() {
        $this->_defaultFilter   =   Testing_FIT_TypeFilter::create( '_default' );
    }

   /**
    * iterate through table 
    * 
    * @param object $node
    * @return boolean true on success
    * @see doRows()
    */
     public function doTable( Testing_FIT_Node $node ) 
     {
        return $this->doRows( $node );
     }

   /**
    * iterate through rows
    * 
    * @param object $node
    * @return boolean true on success
    * @see doRow()
    */
    public function doRows( Testing_FIT_Node $node ) 
    {
        // start at second - the first simply contains the fixture with parameter
        $node->next();
        
        while( $node->valid() ) {
            if( !$this->doRow( $node ) ) {
                return false;
            }
            $node->next();
        }
        
        return true;
    }   

   /**
    * iterate through cells
    * 
    * @param object $node 
    * @return boolean true on success
    * @see doCells()
    */
    public function doRow( Testing_FIT_Node $node  ) 
    {
        return $this->doCells( $node );
    }
     
   /**
    * process cells
    *
    * Generic processing of all upcoming cells. Actually, this method
    * just iterates through them and delegates to doCell()
    *
    * This method may be overwritten by a subclass (ActionFixture)
    * 
    * @param object $node 
    * @return boolean true on success
    * @see doCell()
    */
    public function doCells( Testing_FIT_Node $node ) 
    {
        $cells  = $node->getRowIterator();
        foreach( $cells as $no => $cdata ) {
            try {
                if( !$this->doCell( $cells ) ) {
                    return false;
                }
            }
            catch( Exception $e ) {
                $cells->markException( $e );
            }
        }
        
        return true;
    }    
     
   /**
    * process a single cell
    *
    * Generic processing of a table cell. Well, this function 
    * just ignores cells. 
    * 
    * This method may be overwritten by a subclass (ColumnFixture)
    * 
    * @param object $cell A parse object 
    * @return boolean true on success
    */
    public function doCell( Testing_FIT_Node $node ) 
    {
        $node->markIgnore();
        return true;
    }
    
   /**
    * load a fixture by java-stylish name (dot-separated)
    * 
    * A fixture name might be something like: eg.net.Simulator. This will
    * load eg/net/Simulator.php and intanciates the clss eg_net_Simulator. The path name
    * is realtive to the basic fixture dir.
    *
    * It also supports loading standard fixtures. They are recognized by the prefix: "fit."
    * Those fixtures are maped to the corresponding class.
    * 
    * @param string fixtureName
    * @return object Fixture
    */
    public static function loadFixture( $fixtureName ) 
    {
        // load a FIT standard fixture
        if( strncmp( 'fit.', $fixtureName, 4 ) == 0 ) {
            $class          = array( 'Testing', 'FIT', 'Fixture' );

            // strip leading "fit."
            $fixtureName    = substr( $fixtureName, 4 );

            // strip Fixture from fixtureName
            array_push( $class, str_replace( 'Fixture', '', $fixtureName ) );
            
            $file   = implode( '/', $class );
            $class  = implode( '_', $class );
        }
        else {
            $class  = str_replace( '.', '_', $fixtureName );
            $file   = TESTING_FIT_FIXTURE_DIR . '/' . str_replace( '_', '/', $class );
        }
    
        // load class
        if( !include_once $file . '.php'  ) {
            throw new Exception( 'Could not load Fixture ' . $fixtureName . 'from ' . $file . '.php' );
        }
        
        // instanciate 
        $fix = new $class();
        return $fix;
    }
    
   /**
    * receive member variable's type specification
    * 
    * Use the helper property $_typeDictionary to figure out what type
    * a variable is.
    * 
    * Type is one of:
    *  - integer
    *  - string
    *  - array
    *  - object
    *  - object:CLASSNAME 
    *  - callable
    * 
    * @todo As PHP does automatica type conversation, I reckon this can be spared
    * @return string $type
    */       
    public function getType( $name ) 
    {
        if( !isset( $this->_typeDictionary[$name] ) ) {
            throw new Exception( 'Property has no definition in $_typeDictionary! ' .  get_class( $this ) . '->' . $name );
            return null;
        }

        return $this->_typeDictionary[$name];
    }
    
   /**
    * check a cell's actual against expected value
    * 
    * This uses type filter to create PHP-a-like data before comparision
    * 
    * @param object $node 
    * @param mixec $actual the current value to check against for
    * @return boolean true if actual result matches
    */   
    protected function _checkCell( $node, $actual )
    {
        $filter = $this->_defaultFilter;
        if( isset( $this->_columnFilter[$node->key()] ) ) {
            $filter     =   $this->_columnFilter[$node->key()];
        }
        if( $filter->isEqual( $node->cData, $actual ) ) {
            $node->markRight();
            return true;
        }

        $node->markWrong( $actual );
        return true;
    }
    
   /**
    * bind columns of table header to functions and properties
    * 
    * @param object $node
    * @return boolean true on success
    */
    protected function _bind( Testing_FIT_Node $node ) 
    {
        $this->_columnBindings  = array();
        $this->_columnFilter    = array();
        
        $cells  = $node->getRowIterator();
        foreach( $cells as $no => $name ) {
        
            try {
                $type               = $this->getType( $name );
                $binding            = array();
                $binding['type']    = 'property';
                $binding['name']    = $name;
                
                if( substr( $name, -2 ) == '()' ) {
                    $binding['type']    = 'method';
                    $binding['name']    = substr( $name, 0, -2 );
                }
                
                $this->_columnBindings[$no] =   $binding;
                $this->_columnFilter[$no]   =   Testing_FIT_TypeFilter::create( $type );
                
            }
            catch( Exception $e ) {
                $cells->markException( $e );
            }
        }

        return true;
    }
    
   /**
    * CamelCaseString auxiliary function
    * 
    * @param string $string
    * @return string 
    */   
    public static function camel( $src ) 
    {
        $list   = explode( ' ', $src );
        $des    = '';
        foreach( $list as $s ) {
            $s  = trim( $s );
            if( empty( $s ) ) {
                continue;
            }
            $des  .=    ucfirst( strtolower( $s ) );
        }
        return $des;
    }
}
?>