<?php
/**
 * FIT Parser Node Iterator
 * 
 * $Id$
 * 
 * @author gERD Schaufelberger <gerd@php-tools.net>
 * @package FIT
 * @subpackage Parser
 * @license LGPL http://www.gnu.org/copyleft/lesser.html
 */
 
/**
 * FIT Parser Node Iterator
 * 
 * @version 0.2.0
 * @package FIT
 * @subpackage Fixture
 */
class Testing_FIT_Node implements Iterator
{
   /**
    * make the include folder available for user's fixtures
    * @var array
    */
    private static $_markAttributes  =   array(
                                    'ignore'    => array( 'style' => 'background-color:#EFEFEF;' ),
                                    'right'     => array( 'style' => 'background-color:#CFFFCF;'),
                                    'wrong'     => array( 'style' => 'background-color:#FFCFCF;' ),
                                    'error'     => array( 'style' => 'background-color:#FFFFCF;' ),
                                    'exception' => array( 'style' => 'background-color:#FFFFCF;' ),
                                    'label'     => array( 'style' => 'color:#C08080;font-style:italic;font-size:small;' )
                                    );

   /**
    * parser holding tables
    * @var object
    */
    private $_parser = null;
    
   /**
    * current table
    * @var int
    */
    private $_table = null;
    
   /**
    * current row in table
    * @var int
    */
    private $_row = null;

   /**
    * current cel in row
    * @var int
    */
    private $_col = null;

   /**
    * current pointer position
    * @var int
    */
    private $_pointer = null;

   /**
    * contain the time measurement
    * [startTest]       => start time of whole test
    * [startTestFunc]   => start time test function
    * [endTestFunc]     => end time test function
    * [elapsedTime]     => elapsed time of test function
    * @var array
    */
    private static $_time = array(
                        'start'     => -1,
                        'funcStart' => -1,
                        'funcEnd'   => -1,
                        'elapsed'   => -1
                          );

   /**
    * summary parser for fit.summary
    * @var object
    */
    private static $_summaryParser = null;

   /**
    * initialize node
    *
    * Initialize node iterator. Choose a row in case you want to iterate columns
    * 
    * @param object $parser
    * @param int $table index of table
    * @param int $row index of row in table
    */
    public function __construct( $parser, $table, $row = null )
    {
        $this->_parser  = $parser;
        $this->_table   = $table;
        $this->_row     = $row;
        $this->_pointer = 0;
        
        // copy parser
        if( !self::$_summaryParser ) {
            self::$_summaryParser = clone $this->_parser;
       }

    }

   /**
    * Receive a node object that allows to iterate throuch columns
    *
    * @return object $node
    */
    public function getRowIterator() 
    {
        if( $this->_row !== null ) {
            return null;
        }
        $node = new Testing_FIT_Node( $this->_parser, $this->_table, $this->_pointer );

        if( self::$_time['start'] == -1 ) {
            self::$_time['start'] = microtime( true );
        }

        return $node;
    }
    
   /**
    * magic getter function
    * 
    * Simple access to "cData", "attributes" and "tag" and node position
    *
    * @param string $name of property
    * @return mixed $value
    */
    public function __get( $name ) 
    {
        // node position
        if( $name == 'table' ) {
            return $this->_table;
        }
        
        if( $name == 'row' ) {
            if( $this->_row === null ) {
                return $this->_pointer;
            }
            return $this->_row;
        }
        
        if( $name == 'column' ) {
            return $this->_pointer;
        }
    
        // normal node properties
        if( !in_array( $name, array( 'tag', 'cData', 'attributes' ) ) ) {
            return null;
        }
    
        if( $this->_row === null ) {
            $node   =& $this->_parser->getNodeValue( $name, $this->_table, $this->_pointer );
        }
        else {
            $node   =& $this->_parser->getNodeValue( $name, $this->_table, $this->_row, $this->_pointer );
        }
        
        return $node;
    }
    
   /**
    * magic setter function
    *
    * Simple access to "cData", "attributes" and "tag"
    *
    * @param string $name of property
    * @param mixed $value  
    * @return void   
    */
    public function __set( $name, $value ) 
    {
        if( !in_array( $name, array( 'tag', 'cData', 'attributes' ) ) ) {
            return;
        }
        if( $this->_row === null ) {
            $node   =&  $this->_parser->getNodeValue( $name, $this->_table, $this->_pointer );
        }
        else {
            $node   =&  $this->_parser->getNodeValue( $name, $this->_table, $this->_row, $this->_pointer );
        }
        
        $node   = $value;
        return;
    }
    
   /**
    * Mark node: right
    *
    * @return bool true on success
    */
    public function markRight() 
    {
        $this->_setSummaryInfo( 'right' );
        return $this->mark( 'right' );
    }

   /**
    * Mark node: wrong
    *
    * @param mixed $actual value
    * @return bool true on success
    */
    public function markWrong( $actual = null ) 
    {
        if( !$actual ) {
            $this->_setSummaryInfo( 'wrong' );
            return $this->mark( 'wrong' );    
        }
        
        $escape =   $this->_escape( $actual );
        $this->_setSummaryInfo( 'wrong', $msg );
        $msg    =   $this->_label( 'expected' ) . '<hr />' . $escape . $this->_label( 'actual' );
        return $this->mark( 'wrong', $msg );
    }
    
   /**
    * Mark node: error
    *
    * @param string $msg optional error message
    * @return bool true on success
    */
    public function markError( $msg = null ) 
    {
        $cData  =   $this->_parser->getNodeValue( 'cData', $this->_table, $this->_row, $this->_pointer );
        if( $cData == 'error' ) {
            return $this->markRight();
        }

        if( !empty( $msg ) ) {
            $msg    =   '<hr />' . $msg;
        }

        $this->_setSummaryInfo( 'error', $msg );
        $this->mark( 'error', $msg );
        return true;
    }
    
   /**
    * Mark node: exception happend
    *
    * @param object $e exeption
    * @return bool true on success
    */
    public function markException( $e ) 
    {
        $msg    =   $e->getMessage();
        $this->_setSummaryInfo( 'exception', $msg );
        $msg    = '<hr />' . $msg;
        return $this->mark( 'exception', $msg );
    }
    
   /**
    * Mark node: ignored
    *
    * @param object $node
    * @return bool true on success
    */
    public function markIgnore() 
    {
        $this->_setSummaryInfo( 'ignore' );
        $this->mark( 'ignore' );
        return true;
    }
    
   /**
    * Mark node: info
    *
    * @param string $msg optional error message
    * @return bool true on success
    */
    public function markInfo( $msg = null ) 
    {
        $this->_setSummaryInfo( 'info', $msg );
        if( !empty( $msg ) ) {
            $msg    ='<hr />' . $msg;
        }
        
        $this->mark( 'info', $msg );
        return true;
    }
   
   /**
    * Mark node: label
    *
    * @param string $msg optional error message
    * @return bool true on success
    */
    public function markLabel( $msg = null ) 
    {
        $msg    = $this->_label( $msg );
        return $this->mark( null, $msg );
    }
    
   /**
    * Mark this node
    *
    * @param string $att attribute set to mark with, or null if no attributes should be added at all
    * @param string $msg additional message
    * @return string 
    */
    public function mark( $att, $msg = null ) 
    {
        if( $this->_row === null ) {
            $cells  =   $this->getRowIterator();
            while( $cells->valid() ) {
                $cells->mark( $att, $msg );
                $cells->next();
            }
            return true;
        } 
        
        // or a single cell
        $atts   =& $this->_parser->getNodeValue( 'attributes', $this->_table, $this->_row, $this->_pointer );
        $atts   = array_merge( $atts, self::$_markAttributes[$att] );

        if( $msg === null ) {
            return true;
        }

        $cData  =& $this->_parser->getNodeValue( 'cData', $this->_table, $this->_row, $this->_pointer );
        $cData  .= $msg;

        return true;
    }
   
   /**
    * Turn string into label
    *
    * @param string $msg label message
    * @return string 
    */
    private function _label( $msg = null ) 
    {
        if( !strlen( $msg ) ) {
            return '';
        }
        
        $atts   =   array();
        foreach( self::$_markAttributes['label'] as $name => $value ) {
            array_push( $atts, $name . '="' . $value  . '"' );
        }
        return sprintf( '<span %s>%s</span>', implode( ' ', $atts ), $this->_escape( $msg ) );
    }
    
   /**
    * HTML style string escape
    *
    * @param string $msg string to be escaped
    * @return string 
    */
    private function _escape( $msg = null ) 
    {
        if( $msg === null ) {
            return 'null';
        }
        if( $msg === true ) {
            return 'true';
        }
        if( $msg === false ) {
            return 'false';
        }
        if( !strlen( $msg ) ) {
            return '';
        }
        return htmlspecialchars( $msg );
    }
   
   /**
    * Iterator interface: current
    * 
    * @return mixed $data cDate in case of column, amount of children in case of row
    */
    public function current() 
    {
        if( $this->_row === null ) {
            // iterate through rows
            return $this->_parser->countChildNodes( $this->_table, $this->_pointer );
        }
        
        // iterate through columns
        return $this->_parser->getNodeValue( 'cData', $this->_table, $this->_row, $this->_pointer );
    }
    
   /**
    * Iterator interface: next
    * 
    * Move pointer to next entry
    * @return void
    */
    public function next() 
    {
        ++$this->_pointer;
    }
    
   /**
    * Iterator interface: key
    * 
    * Receive internal pointer
    * @return int $pointer
    */
    public function key() 
    {
        return $this->_pointer;
    }
    
   /**
    * Iterator interface: valid
    * 
    * Check whether current pointer is still in range
    * @return void
    */
    public function valid() 
    {
        if( $this->_row === null ) {
            // iterate through rows
            if( $this->_pointer < $this->_parser->countChildNodes( $this->_table ) ) {
                return true;
            }            
            return false;
        }
        
        // iterate through columns
        if( $this->_pointer < $this->_parser->countChildNodes( $this->_table, $this->_row ) ) {
            return true;
        }            
        return false;
    }
    
   /**
    * Iterator interface: rewind
    * 
    * Reset pointer
    * @return void
    */
    public function rewind() 
    {
        $this->_pointer = 0;
    }

   /**
    * set time function 
    *
    * get duration of testing a function
    * @return void
    */
    private function _setTime()
    {
        self::$_time['funcEnd'] = microtime( true );
        if( self::$_time['funcStart'] == -1 ) {
            self::$_time['funcStart'] = self::$_time['start'];
        }

        self::$_time['elapsed']     = self::$_time['funcEnd'] - self::$_time['funcStart'];
        self::$_time['funcStart']   = self::$_time['funcEnd'];
    }

   /**
    * set summary information
    * 
    * write to $_summaryParser info data for summary
    * @param string $type one of the types used in mark: "error", "exception", "ignore",
    * @param string $msg optional message 
    * @param array $msg     contain some information of testresult
    *                       'cData'     of cell
    *                       'message'   can be right, wrong, ignore, exception
    *                       'error'     error-message
    *                       'exception' exception-message
    *                       'time'      duration of test
    * @return bool true on usccess
    */
    private function _setSummaryInfo( $type, $msg = null )
    {
        $this->_setTime();

        // append rows if required
        $counter    =   $this->_row;
        if( $this->_row == null ) {
            $counter    =   $this->_pointer;
        }
        if( $counter >= self::$_summaryParser->countChildNodes( $this->_table ) ) {
            self::$_summaryParser->appendRow( $this->_table );
        }

        // to mark as summary table
        $summaryVal = self::$_summaryParser->getNodeValue( 'attributes', $this->_table, 0, 0 );
        if( empty( $summaryVal['noSummary'] ) ) {
            $attrib = $summaryVal;
            $attrib['noSummary'] = true;
            self::$_summaryParser->setNodeValue( 'attributes', $attrib, $this->_table, 0, 0 );
        }

        $cData  =   null;
        if( $this->_row !== null ) {
            $this->_parser->getNodeValue( 'cData', $this->_table, $this->_row, $this->_pointer );
        }
        
        $results    =   array(
                'cData'     =>  $cData,
                'type'      =>  $type,
                'message'   =>  $msg,
                'time'      =>  array(
                                    'funcStart' => self::$_time['funcStart'],
                                    'funcEnd'   => self::$_time['funcEnd'],
                                    ),
                );
        
        $data   =   array(
                    'attributes'    =>  self::$_markAttributes[$type]['style'],
                    'results'       =>  $results
                    );
        
        if( $this->_row !== null ) {
            self::$_summaryParser->setNodeValue( 'attributes', $data, $this->_table, $this->_row, $this->_pointer );
        } else {
            self::$_summaryParser->setNodeValue( 'attributes', $data, $this->_table, $this->_pointer );
        }
        return true;
    }

   /**
    * get summary parser
    *
    * public function for summary fixture to get summary parser
    * @see Summary.php
    * @return object $_summaryParser
    */
    public function getSummary()
    {
        return self::$_summaryParser;
    }

   /**
    * get parser
    *
    * public function for summary fixture to get parser
    * @see Summary.php
    * @return object $_parser
    */
    public function getParser()
    {
        return $this->_parser;
    }
}
?>