<?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 doTableTesting_FIT_Node $node 
     {
        return 
$this->doRows$node );
     }

   
/**
    * iterate through rows
    * 
    * @param object $node
    * @return boolean true on success
    * @see doRow()
    */
    
public function doRowsTesting_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 doRowTesting_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 doCellsTesting_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 doCellTesting_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) == ) {
            
$class          = array( 'Testing''FIT''Fixture' );

            
// strip leading "fit."
            
$fixtureName    substr$fixtureName);

            
// strip Fixture from fixtureName
            
array_push$classstr_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 _bindTesting_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, -) == '()' ) {
                    
$binding['type']    = 'method';
                    
$binding['name']    = substr$name0, -);
                }
                
                
$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  .=    ucfirststrtolower$s ) );
        }
        return 
$des;
    }
}
?>