<?php
/**
 * FIT Fixture
 * 
 * $Id$
 * 
 * @author Luis A. Floreani <luis.floreani@gmail.com>
 * @author gERD Schaufelberger <gerd@php-tools.net>
 * @package FIT
 * @subpackage Fixture
 * @license LGPL http://www.gnu.org/copyleft/lesser.html
 * @copyright Copyright (c) 2002-2005 Cunningham & Cunningham, Inc.
 */
 
/** 
 * path to user's fixtures
 */
if( !defined'PHPFIT_FIXTURE_DIR' ) ) {
    
define'PHPFIT_FIXTURE_DIR''.' );
}

/**
 * load counter
 */
include_once 'PHPFIT/Counts.php';

/**
 * load scienttific double class
 */
include_once 'PHPFIT/ScientificDouble.php';

/**
 * load timer class
 */
include_once 'PHPFIT/RunTime.php';

/**
 * FIT Fixture
 * 
 * @version 0.1.0
 * @package FIT
 * @subpackage Fixture
 */
class PHPFIT_Fixture {

   
/**
    * make the include folder available for user's fixtures
    * @var array
    */
    
protected $backgroundColor  =   array(
                                    
'passed'    => '#cfffcf',
                                    
'failed'    => '#ffcfcf',
                                    
'ignored'   => '#efefef',
                                    
'error'     => '#ffffcf',
                                    );

   
/**
    * collecting information of this fixture
    * @var array
    */
    
public $summary = array();
    
   
/**
    * count what?
    * @var object
    */     
    
public $counts;
    
   
/**
    * construtor
    * 
    * instanciate counter
    */   
    
function __construct() {
        
$this->counts = new PHPFIT_Counts();
    }

   
/**
    * Traverse all tables
    * 
    * Tables are packed in Parse-objects
    * 
    * @param Parse $tables
    */   
    
public function doTables$tables ) {

        
$this->summary['run date'] = date'F d Y H:i:s.' );
        
$this->summary['run elapsed time'] = new PHPFIT_RunTime();   

        
// no tables left
        
if( $tables == null ) {
            return;
        }
        
        
// iterate through all tables
        
while( $tables != null ) {
            
$fixtureName $this->fixtureName$tables );
            
            if( 
$fixtureName == null ) {
                
$tables $tables->more;
                continue;
            }
            
            try {
                
$fixture $this->getLinkedFixtureWithArgs$tables );
                
$fixture->doTable$tables );
            }
            catch( 
Exception $e ) {
                
$this->exception$fixtureName$e );
            }
            
$tables $tables->more;        
        }
    }

   
/**
    * iterate through table 
    * 
    * @param Parse $table
    * @see doRows()
    */
     
public function doTable$table 
     {
        
$this->doRows$table->parts->more );
     }

   
/**
    * iterate through rows
    * 
    * @param Parse $rows
    * @see doRow()
    */
    
public function doRows$rows 
    {
        while( 
$rows != null ) {
            
$more $rows->more;
            
$this->doRow$rows );
            
$rows $more;
        }
     }     

   
/**
    * iterate through cells
    * 
    * @param Parse $row
    * @see doCells()
    */
    
public function doRow$row 
    {
        
$this->doCells$row->parts );
    }
     
   
/**
    * 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 $cells A parse object 
    * @return void
    * @see doCell()
    */
    
public function doCells$cells 
    {
        while( 
$cells ) {
            try {
                
$this->doCell$cells );
            } 
            catch( 
Exception $e ) {
                
$this->exception$cells$e );
            }
            
            
$cells $cells->more;
        }
    }     
     
   
/**
    * 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 void
    */
    
public function doCell$cell 
    {
        
$this->ignore$cell );
    }

   
/**
    * find the name of the fixture to be executed
    *
    * @param parse $tables
    * @return string $name of the fixure
    */
    
public function fixtureName$tables 
    {
        return 
$tables->at00);
    }

   
/**
    * get a fixture with arguments
    * 
    * @param Parse tables
    * @return object Fixture
    * @see loadFixture()
    */
    
protected function getLinkedFixtureWithArgs$tables 
    {
        
$header           $tables->at00);
        
$fixture          $this->loadFixture$header->text() );
        
$fixture->counts  $this->counts;
        
$fixture->summary $this->summary;
        return 
$fixture;
    }

   
/**
    * load a fixture by java-stylish name (dot-sparated)
    * 
    * 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 function loadFixture$fixtureName 
    {
        
// load a FIT standard fixture
        
if( strncmp'fit.'$fixtureName) == ) {
        
            
// strip leading "fit."
            
$fixtureName    substr$fixtureName);
        
            
$class  = array( 'PHPFIT''Fixture' );
            
            
// strip Fixture from fixtureName
            
array_push$classstr_replace'Fixture'''$fixtureName ) );
            
/*
            $pos    = strpos( $fixtureName, 'Fixture' );
            if( $pos !== false ) {
                $fixtureName    = substr(  $fixtureName, $pos, 7 );
            }
            
            array_push( $class, $fixtureName );
            */
            
$file   implode'/'$class );
            
$class  implode'_'$class );
        }
        else {
            
$class  str_replace'.''_'$fixtureName );
            
$file   PHPFIT_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;
    }

   
/**
    * check whether the value of a cell matches
    * 
    * @param Parse $cell,
    * @param TypeAdapter $a
    * @return bool true on success, false otherwise
    */
    
public function checkCell$cell$a 
    {
        
$text $cell->text();
        
        if( 
$text == '' ) {
        
            
// there is no adapter
            
if( $a == null ) {
                
$this->info$cell'error' );
                return 
false;
            }
            
            try {
                
$this->info$cell$a->toString$a->get() ) );
            } 
            catch( 
Exception $e ) {
                
$this->info$cell'error' );
            }
            return 
true;                     
        } 
        
        if( 
$a == null ) {
            
$this->ignore$cell );
            return 
true;
        }
        
        if( 
strncmp$text'error') == ) {
            try {
                
$result $a->invoke();
                
$this->wrong$cell$a->toString() );
            }
            catch( 
Exception $e ) {
                
$this->right$cell );
            }         
            return 
true;            
        } 
        
        try {
            
// the value of the attribute or the return value of the method
            
$result $a->get(); 
            
            if( 
$a->equals$a->parse$text ), $result ) ) {
                
$this->right$cell );
            } 
            else {
                
$result $this->fixBoolToString$result );
                
$this->wrong$cell$a->toString$result ) );
            }
        }
        catch( 
Exception $e ) {
            
$this->exception($cell$e);
        }
        
        return 
true;
    }
    
   
/**
    * convert a  boolean value to corresponding string
    * 
    * @param bool $bool a boolean value
    * @return string "true" or "false"
    */   
    
public function fixBoolToString$bool 
    {
        if( !
is_bool$bool ) ) { 
            return 
$bool;        
        }
        
        if( 
$result ) {
            return 
'true';
        }
        
        return 
'false';
    }
   
   
/**
    * transform an exception to a cell error 
    * 
    * @param object $cell Parse object
    * @param object $e Exception
    * @see error()
    */
    
public function exception$cell$e 
    {
        
$this->error$cell$e->getMessage() );
    }

   
/**
    * place an error text into a cell 
    * 
    * @param object $cell Parse object
    * @param string $message 
    */
    
public function error$cell$message 
    {
        
$cell->body   $cell->text() . ': '$this->escape$message );
        
$cell->addToTag' bgcolor=" '$this->backgroundColor['error'] . '\"' );
        
$this->counts->exceptions++;
    }

   
/**
    * parse value from cell
    * 
    * @param string s
    * @param string type
    * @return mixed (object or string)
    */
    
public function parse$s$type 
    {
        if( 
$type == 'ScientificDouble' ) {
            return 
PHPFIT_ScientificDouble::valueOf$s );
        }
        return 
$s;
    }
    
   
/**
    * Add annotation to cell: right
    * 
    * @param Parse c$ell
    * @param string type
    * @return mixed (object or string)
    */
     
public function right$cell 
     {
         
$cell->addToTag' bgcolor="' $this->backgroundColor['passed'] . '"' );
         
$this->counts->right++;
     }

     
/**
     * @param Parse cell
     * @param string actual
     */
     
     
public function wrong$cell$actual false 
     {
         
$cell->addToTag' bgcolor="' .  $this->backgroundColor['failed'] . '"' );
         
$cell->body  $this->escape$cell->text() );
         
$this->counts->wrong++;
         
         if( 
$actual !== false ) {
             
$cell->addToBody$this->label'expected' ) . '<hr />' $this->escape$actual ) . $this->label'actual' ) );
         }        
     }     
     
     
    
/**
     * @param Parse cell
     * @param string message
     */
     
     
public function info$cell$message 
     {
         
$str $this->infoS$message );
         
$cell->addToBody$str );
     }
     
     
    
/**
     * @param string message
     * @return string
     */
     
     
public function infoS$message ) {
         return 
' <span style="color:#808080;">' $this->escape$message ) . '</span>';
     }
     
     
/**
     * @param Parse cell
     */

     
public function ignore ($cell) {
         
$cell->addToTag' bgcolor="' $this->backgroundColor['ignored'] . '"' );
         
$this->counts->ignores++;
     }
     
     
    
/**
     * @param string string
     * @return string
     */
     
    
public function label$string ) {
        return 
' <span style="color:#c08080;font-style:italic;font-size:small;">' $string '</span>';
    }
        
    
/**
     * @param string string
     * @return string
     */
     
    
public function escape($string) {
        
$string str_replace('&''&amp;'$string);
        
$string str_replace('<''&lt;'$string);
        
$string str_replace('  '' &nbsp;'$string);
        
$string str_replace('\r\n''<br />'$string);
        
$string str_replace('\r''<br />'$string);
        
$string str_replace('\n''<br />'$string);
        return 
$string;
    }
    
   
/**
    * receive member variable's type specification
    * 
    * Use the helper property typeDict to figure out what type
    * a member variable or return value of a member function 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
    * @param string $name of property or method
    * @param bool $method check for return type of method
    * @return string $type
    */       
    
public function getType$name$method false ) {
    
        
// method 
        
if( $method ) {
            if( !
method_exists$this$name ) ) {
                throw new 
Exception'Method does not exist! ' .get_class$this ) . '->' $name );
                return 
null;
            }
            
$name .= '()';
        }
        
// property
        
else {    
            if( !
property_exists$this$name ) ) {
                throw new 
Exception'Property does not exist! ' .get_class$this ) . '->' $name );
                return 
null;
            }
       }
        
        if( !isset( 
$this->typeDict[$name] ) ) {
            throw new 
Exception'Property has no definition in $typeDict! ' get_class$this ) . '->' $name );
            return 
null;
        }
        
        return 
$this->typeDict[$name];
    }
   
   
/**
    * CamelCaseString auxiliary function
    * 
    * @todo This looks quite fragile - consider using preg_replace
    * @param string $string
    * @return string 
    */   
    
public static function camel$string 
    {
        while( ( 
$pos stripos($string' ' ) ) !== false ) {
            
$characterUpper strtoupper$string[$pos+1] );
            
$string[$pos+1] = $characterUpper;
            
$string[$pos] = "&";
        }
        
        
$string str_replace('&'''$string);
        return 
$string;
    }
}
?>