<?php

defined('WikyBlog') or die("Not an entry point...");


///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//
//			difference_main
//


class difference_main{
	
	var $instructions;
	var $keys;
	var $differences = 0;
	
	function difference_main($source,$result){
		$instruction = array();
		
		//	1)	if Objects => arrays
		if( is_object($result) ){
			$type = get_class( $result );
			$type2 = get_class( $source );
			if( $type !== $type2){
				trigger_error('Objects are not of the same type '.$type.' :: '.$type2);
			}
			
			$source = get_object_vars( $source );
			$result = get_object_vars( $result );
		}
		
		//	2)	Make instructions for each field
		$workingClass = new getInstructions();
		
		
		foreach($result as $key => $resultValue){
			$sourceValue = $source[$key];
			
			if( empty($resultValue) ){
				$resultValue = '';
			}
			if( empty($sourceValue) ){
				$sourceValue = '';
			}
				
			$workingClass->calcDifference($sourceValue, $resultValue);
			$workingClass->setInstructions();
			
			if( isset($workingClass->instructions) ){
				
				$instructions[$key] = $workingClass->instructions;
				$this->keys[] = $key;
				$this->differences += $workingClass->differences;
			}
			
		}
		
		//	3)	Serialize, Compress and return
		if( !empty($instructions) ){
			//echo showArray($instructions);
			$instructions = serialize($instructions);
		 	$this->instructions = $instructions;
		 	if( function_exists('gzdeflate') ){
			 	$this->instructions = gzdeflate($this->instructions);
		 	}
	 	}
	}
	
}




//
//			difference_main
//
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//
//			difference
//	




class difference{
	
	////////////////////////////////////////////////////////////////////////////
	////
	////		VARIABLES
	////
	var $source;
	var $result;
	
	var $left;
	var $leftIndex;
	var $maxleft;
	
	var $right;
	var $rightIndex;
	var $maxRight;
	
	var $deleteLeft;
	var $addRight;
	var $instructions;
	
	var	$debugText;
	var $differences;
	

	function resetVars(){
		$array =  get_class_vars( get_class($this) );
		foreach($array as $key => $value){
			unset($this->$key);
		}
		//$this->differences = 0;
		$this->leftIndex = 0;
		$this->rightIndex = 0;
		$this->deleteLeft = array();
		$this->addRight = array();
	}

	////////////////////////////////////////////////////////////////////////////
	////
	////		This Does the Work
	////			$left = source; $right = result;
	////
	
	function calcDifference($left,$right,$doBlankLines=true){ 

		// 0)	 don't bother getting instructions if result is very small
			// this doesn't exactly work because of later comparison...	
// 		if( wbStrlen($right) < 80 ){
// 			$this->instructions = $right;
// 			return;
// 		}
		$this->resetVars();
		
		$this->source = $left;
		$this->result = $right;

		$this->left = stringToArray($left);
		$this->right = stringToArray($right);
		
		$this->maxLeft = count($this->left);
		$this->maxRight = count($this->right);
		
		/// should use a foreach(..) here... I think they're faster too
		
		while( ($this->leftIndex < $this->maxLeft) or ($this->rightIndex < $this->maxRight) ){
			//echo $this->debugText;
			//$this->debugText = '<p>';
			
			////////////////////////////////////////////////////////////////////////////
			////
			////		I)	End of left or right
			////
			// left at max
			if( $this->leftIndex == $this->maxLeft ){
				//$this->debugText .= '<p>LeftIndex = max = '.$this->maxLeft;
				while($this->rightIndex < $this->maxRight){
					$this->addRight();
				}
				break;
			}
			// right at max
			if( $this->rightIndex == $this->maxRight ){
				//$this->debugText .= '<p>RightIndex = max = '.$this->maxRight;
				while($this->leftIndex < $this->maxLeft){
					$this->deleteLeft();
				}
				break;
			}
			
	
			////////////////////////////////////////////////////////////////////////////
			////
			////		II)	left == right
			////
			
			if( $this->left[$this->leftIndex] == $this->right[$this->rightIndex] ){
				//$this->debugText .= '<p>Equal at left: '.$this->leftIndex.' &nbsp;&nbsp;&nbsp;Right: '.$this->rightIndex;
				unset($this->left[$this->leftIndex]); 
				unset($this->right[$this->rightIndex]);
				$this->leftIndex++;
				$this->rightIndex++;
				continue;
			}
			
			////////////////////////////////////////////////////////////////////////////
			////
			////		III) left != right
			////
			
			if($doBlankLines){
				//	2) Add or Delete Blank Lines
				if( empty($this->left[$this->leftIndex]) || $this->left[$this->leftIndex] == ''){
					//$this->debugText .= '<br/><b>Empty line on left at</b> '.$this->leftIndex;
					$this->deleteLeft();
					continue;
				}
				if( empty($this->right[$this->rightIndex]) || $this->right[$this->rightIndex] == ''){
					//$this->debugText .= '<br/><b>Empty line on right at</b> '.$this->rightIndex;
					$this->addRight();
					continue;
				}
			}
			
						
			//	3)	Distance to next similarity
			$rightMatchAt = false;
			$leftMatchAt = false;
			$rightMatchAt = array_search($this->left[$this->leftIndex],$this->right);
			$leftMatchAt = array_search($this->right[$this->rightIndex],$this->left);
			//$this->debugText .= '<p>Left: '.$this->leftIndex. ' &nbsp;&nbsp;&nbsp;Right-Match At: '.$rightMatchAt .' &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Right: '.$this->rightIndex. ' &nbsp;&nbsp;&nbsp;Left-Match at: '.$leftMatchAt;
			
			//	4)	left never equals right with either line: 
			//			- this should prevent both differences from being negative below
			if( ($rightMatchAt === false) && ($leftMatchAt === false)){
				$this->addRight();
				$this->deleteLeft();
				continue;
			}
			if($rightMatchAt === false){
				$this->deleteLeft();
				continue;
			}
			if($leftMatchAt === false){
				$this->addRight();
				continue;
			}		

			$numAdds = ($rightMatchAt - $this->rightIndex);
			$numDeletes = ($leftMatchAt - $this->leftIndex);;
			
			//$this->debugText .= '<br/>&nbsp;&nbsp;&nbsp;Number of Adds: '.$numAdds;
			//$this->debugText .= '<br/>&nbsp;&nbsp;&nbsp;Number of Deletes: '.$numDeletes;
			
			if( (($numAdds>0) && ($numAdds<=$numDeletes)) || ($numDeletes<0) ){
				while($this->rightIndex < $rightMatchAt){
					$this->addRight();
				}
			}elseif($numDeletes >= 0){
				while($this->leftIndex < $leftMatchAt){
					$this->deleteLeft();
				}
			}
			
		}// 1st while
		
		//echo $this->debugText;
		$this->differences = $this->numberOfDifferences();
		return;
		
	}//end function findDifference
	
	function addRight(){
		//$this->debugText .= '<br/>&nbsp;&nbsp;&nbsp;Add right: (leftIndex) '.$this->leftIndex;
		$this->addRight[$this->leftIndex][$this->rightIndex] = $this->right[$this->rightIndex];
		unset($this->right[$this->rightIndex]);
		$this->rightIndex++;
		//$this->differences++;
	}
	function deleteLeft(){
		//$this->debugText .= '<br/>&nbsp;&nbsp;&nbsp;Delete left: (leftIndex) '.$this->leftIndex;
		$this->deleteLeft[$this->leftIndex] = $this->left[$this->leftIndex];
		unset($this->left[$this->leftIndex]);
		$this->leftIndex++;
		//$this->differences++;
	}
	
	
	function numberOfDifferences(){
		$differences = 0;
 		$temp1 = array_keys($this->deleteLeft);
		$temp2 = array_keys($this->addRight);
 		$intersection = count( array_intersect($temp1,$temp2) );
		$differences = count($this->deleteLeft) + (count($this->addRight,1)-count($this->addRight) );
		$differences -= $intersection;
		return $differences;
	}
	
}


//
//			difference
//
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//
//			getInstructions
//				translates add/delete then serializes



class getInstructions extends difference{
	
	function setInstructions(){
		
		if( empty($this->deleteLeft) && empty($this->addRight) ){
			return;
		}
		
		//	1)	Make Move Instructions
		$deleteTemp = $this->deleteLeft;
		$addTemp = $this->addRight;
		
		$moveTemp = NULL;
		foreach($this->addRight as $leftIndex => $addArray){
			foreach($addArray as $rightIndex => $addLine){
				
				if( ($addLine == '') || empty($addLine)){
					continue;
				}
				
				$moveFrom = false;
				$moveFrom = array_search($addLine,$deleteTemp);
				if( ($moveFrom !== false) && !is_null($moveFrom)){ //is_null for php versions < 4.2
					$moveTemp[$moveFrom]=$leftIndex.'.'.$rightIndex;
					unset($deleteTemp[$moveFrom]);
					unset($addTemp[$leftIndex][$rightIndex]);
				}
			}
		}
		if( !empty($moveTemp) ){
			$array['m'] = $moveTemp;
		}
		
		
		//	2) Replace Instructions
		$replaceTemp = array();
		foreach($deleteTemp as $leftIndex => $deleteLine){
			if( isset($addTemp[$leftIndex]) ){
				foreach($addTemp[$leftIndex] as $addKey => $addLine){
					$replaceTemp[$leftIndex][$addKey] = $addLine;
					//$replaceTemp[$leftIndex] = $addLine;				// other options for saving space? only a few bytes here and there
					//$replaceTemp[$leftIndex.'.'.$addKey] = $addLine;		//
				}
				unset($addTemp[$leftIndex]);
				unset($deleteTemp[$leftIndex]);
			}
		}
		if( !empty($replaceTemp) ){
			$array['r'] = $replaceTemp;
		}
		
		//	3)	Compress Delete Instrucions
		foreach($deleteTemp as $key => $deleteLine){
			$array['d'][$key] = '';
		}
		
		//	4)	Add instructions
		if( !empty($addTemp) ){
			$array['a'] = $addTemp;
		}
		
		//	5)	Check Size of instructions vs just saving value
	 	if( function_exists('gzdeflate') ){
			$resultSize = strlen(gzdeflate($this->result));
			$instructionSize = strlen(gzdeflate( serialize($array) ));
	 	}else{
		 	$resultSize = $this->result;
		 	$instructionSize = strlen(serialize($array));
	 	}

		// echo '<p><b>Sizes</b> (compressed)';
		// echo '<br/>Result Size: '.$resultSize;
		// echo '<br/>Size of Instructions: '.$instructionSize;
		
		if($resultSize <= $instructionSize){
			$this->instructions = $this->result;
		}else{
			$this->instructions = $array;
		}
	}// end setInstructions
}


//
//			getInstructions
//
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
