<?php

	class Debugger 
	{
		/**
		 * Debugger data for building report
		 *
		 * @var Array
		 */
		var $Data = Array();
		var $ProfilerData = Array();
		
		function dumpVars()
		{
			$dumpVars = func_get_args();
			foreach($dumpVars as $varValue)
			{
				$this->Data[] = Array('value' => $varValue, 'debug_type' => 'var_dump');
			}
		}
		
		function prepareHTML($dataIndex)
		{
			$Data =& $this->Data[$dataIndex];
			if($Data['debug_type'] == 'html') return $Data['html'];
			
			switch($Data['debug_type'])
			{
				case 'error':
					$fileLink = $this->getFileLink($Data['file'],$Data['line']);
					$ret = '<b class="debug_error">'.$this->getErrorNameByCode($Data['no']).'</b>: '.$Data['str'];
					$ret .= ' in <b>'.$fileLink.'</b> on line <b>'.$Data['line'].'</b>';
					return $ret;
					break;
				
				case 'var_dump':
					$ret = highlight_string('<?php '.print_r($Data['value'], true).'?>', true);
					$ret = preg_replace('/&lt;\?php (.*)\?&gt;/s','$1',$ret);
					return addslashes($ret);
					break;
					
				case 'trace':
					$trace =& $Data['trace'];
					$i = 0; $traceCount = count($trace);
					$ret = '';
					while($i < $traceCount)
					{
						$traceRec =& $trace[$i];
						$argsID = 'trace_args_'.$dataIndex.'_'.$i;
						$ret .= '<a href="javascript:toggleTraceArgs(\''.$argsID.'\');" title="Show/Hide Function Arguments"><b>Function</b></a>: '.$this->getFileLink($traceRec['file'],$traceRec['line'],$traceRec['class'].$traceRec['type'].$traceRec['function']).'';
						$ret .= ' in <b>'.basename($traceRec['file']).'</b> on line <b>'.$traceRec['line'].'</b><br>';
						
						// ensure parameter value is not longer then 200 symbols
						foreach ($traceRec['args'] as $argID => $argValue)
						{
							if( strlen($argValue) > 200 ) $traceRec['args'][$argID] = substr($argValue,0,50).' ...';	
						}
						$args = highlight_string('<?php '.print_r($traceRec['args'], true).'?>', true);
						$args = preg_replace('/&lt;\?php (.*)\?&gt;/s','$1',$args);
						$ret .= '<div id="'.$argsID.'" style="display: none;">'.$args.'</div>';
						
						$i++;
					}
					/*$ret = highlight_string('<?php '.print_r($trace, true).'?>', true);
					$ret = preg_replace('/&lt;\?php (.*)\?&gt;/s','$1',$ret);*/
					return $ret;
					break;
				
				case 'profiler':
					$profileKey = $Data['profile_key'];
					$Data =& $this->ProfilerData[$profileKey];
					$runtime = ($Data['ends'] - $Data['begins']); // in seconds
					return '<b>Name</b>: '.$Data['description'].'. <b>Runtime</b>: '.$runtime.'s';
					break;
						
				default:
					return 'incorrect debug data';
					break;
			}
		}
		
		function getFileLink($file, $lineno = 1, $title = '')
		{
			if(!$title) $title = $file;
			return '<a href="javascript:editFile(\''.$this->getLocalFile($file).'\','.$lineno.');" title="'.$file.'">'.$title.'</a>';	
		}
		
		function getLocalFile($remoteFile)
		{
			return str_replace(DOC_ROOT, WINDOWS_ROOT, $remoteFile);	
		}
		
		function appendTrace()
		{
			$trace = debug_backtrace();
			array_shift($trace);
			$this->Data[] = Array('trace' => $trace, 'debug_type' => 'trace');
		}
		
		function appendHTML($html)
		{
			$this->Data[] = Array('html' => $html,'debug_type' => 'html');	
		}
		
		function profileStart($key, $description)
		{
			$timeStamp = $this->getMoment();
			$this->ProfilerData[$key] = Array('begins' => $timeStamp, 'ends' => 5000, 'debuggerRowID' => count($this->Data), 'description' => $description);
			$this->Data[] = array('profile_key' => $key, 'debug_type' => 'profiler');
		}
		
		function profileFinish($key)
		{
			$this->ProfilerData[$key]['ends'] = $this->getMoment();
		}
		
		function getMoment()
		{
			list($usec, $sec) = explode(' ', microtime()); 
   			return ((float)$usec + (float)$sec); 
		}
		
		function generateID()
		{
			list($usec, $sec) = explode(" ",microtime());

			$id_part_1 = substr($usec, 4, 4);
			$id_part_2 = mt_rand(1,9);
			$id_part_3 = substr($sec, 6, 4);
			$digit_one = substr($id_part_1, 0, 1);
			if ($digit_one == 0) {
				$digit_one = mt_rand(1,9);
				$id_part_1 = ereg_replace("^0","",$id_part_1);
				$id_part_1=$digit_one.$id_part_1;
			}
			return $id_part_1.$id_part_2.$id_part_3;
		}

		
		function getErrorNameByCode($errorCode)
		{
			switch($errorCode)
			{
				case E_USER_ERROR:
					return 'Fatal Error';
					break;
					
				case E_WARNING:
				case E_USER_WARNING:
					return 'Warning';
					break;
					
				case E_NOTICE:
				case E_USER_NOTICE:
					return 'Notice';
					break;
				
				default:
					return '';
					break;
			}
		}
		
		/**
		 * Generates report
		 *
		 */
		function printReport()
		{
			$i = 0; $lineCount = count($this->Data);
			?>
				<link rel="stylesheet" type="text/css" href="<?php echo DEBUG_CSS; ?>">
				<div id="debug_layer" class="debug_layer_container" style="display: none;">
					<div style="padding: 0px;">
						<table width="100%" cellpadding="0" cellspacing="1" border="0" class="debug_layer_table">
			<?php
			while ($i < $lineCount)
			{
				echo '<tr class="debug_row_'.(($i % 2) ? 'odd' : 'even').'"><td class="debug_cell">'.$this->prepareHTML($i).'</td></tr>';
				$i++;
			}
			?>
						</table>
					</div>
				</div>
				<script language="javascript">
					function getEventKeyCode($e)
					{
						var $KeyCode = 0;
						if($e.keyCode) $KeyCode = $e.keyCode;
						else if($e.which) $KeyCode = $e.which;
						return $KeyCode;
					}
				
					function keyProcessor($e)
					{
						if(!$e) $e = window.event;
						var $KeyCode = getEventKeyCode($e);
						//alert(showProps($e));
						if($KeyCode == 123 || $KeyCode == 68 && $e.shiftKey) // F12 (for Maxthon) or Ctrl+F2 (for Other Browsers)
						{
							toggleDebugLayer();
							$e.cancelBubble = true;
							if($e.stopPropagation) $e.stopPropagation();
						}
					}
					
					function toggleDebugLayer()
					{
						var $DebugLayer = document.getElementById('debug_layer');
						if( typeof($DebugLayer) != 'undefined' )
						{
							resizeDebugLayer(null);
							$DebugLayer.style.display = ($DebugLayer.style.display == 'none') ? 'block' : 'none';
						}
					}
					
					function prepareSizes($Prefix)
					{
						var $ret = '';
						$ret = eval('document.body.'+$Prefix+'Top')+'; ';
						$ret += eval('document.body.'+$Prefix+'Left')+'; ';
						$ret += eval('document.body.'+$Prefix+'Height')+'; ';
						$ret += eval('document.body.'+$Prefix+'Width')+'; ';	
						return $ret;
					}
					
					function resizeDebugLayer($e)
					{
						if(!$e) $e = window.event;
						var $DebugLayer = document.getElementById('debug_layer');
						var $TopMargin = 1;
						if( typeof($DebugLayer) != 'undefined' )
						{
							$DebugLayer.style.top = parseInt(document.body.offsetTop + document.body.scrollTop) + $TopMargin;
							$DebugLayer.style.height = document.body.clientHeight - $TopMargin - 5;
						}
						window.parent.status = 'OFFSET: '+prepareSizes('offset')+' | SCROLL: '+prepareSizes('scroll')+' | CLIENT: '+prepareSizes('client');
						window.parent.status += 'DL Info: '+$DebugLayer.style.top+'; S.AH: '+screen.availHeight;
						return true;
					}
					
					function SetClipboard($data)
					{
						if (window.clipboardData)
						{
							window.clipboardData.setData('Text', $data);
						}
						else if (window.netscape)
						{
							//netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
							var clip = Components.classes['@mozilla.org/widget/clipboard;1'].createInstance(Components.interfaces.nsIClipboard);
							if (!clip) return;

							var trans = Components.classes['@mozilla.org/widget/transferable;1'].createInstance(Components.interfaces.nsITransferable);
							if (!trans) return;

							trans.addDataFlavor('text/unicode');
							var str = new Object();
							var len = new Object();
							var str = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);

							var $copytext=$data;

							str.data=$copytext;

							trans.setTransferData("text/unicode",str,$copytext.length*2);
							var clipid=Components.interfaces.nsIClipboard;
							if (!clip) return false;

							clip.setData(trans,null,clipid.kGlobalClipboard);
						}

					}
					
					function showProps($Obj, $Name)
					{
						var $ret = '';
						for($Prop in $Obj)
						{
							$ret += $Name+'.'+$Prop+' = '+$Obj[$Prop]+"\n";
						}
						return $ret;
					}
					
					function editFile($fileName,$lineNo)
					{
						var $editorPath = '<?php echo defined('WINDOWS_EDITOR') ? addslashes(WINDOWS_EDITOR) : '' ?>';
						if($editorPath)
						{
							var $obj = new ActiveXObject("LaunchinIE.Launch");
							$editorPath = $editorPath.replace('%F',$fileName);
							$editorPath = $editorPath.replace('%L',$lineNo);
							$obj.LaunchApplication($editorPath);
						}
						else
						{
							alert('Editor path not defined!');	
						}
					}
					
					function toggleTraceArgs($ArgsLayerID)
					{
						var $ArgsLayer = document.getElementById($ArgsLayerID);
						$ArgsLayer.style.display = ($ArgsLayer.style.display == 'none') ? 'block' : 'none';
					}
					
					
					document.onkeydown = keyProcessor;
					window.onresize = resizeDebugLayer;
					window.onscroll = resizeDebugLayer;
					window.focus();
					if( typeof($isFatalError) != 'undefined' && $isFatalError == 1 )
					{
						toggleDebugLayer();
						document.getElementById('debug_layer').scrollTop = 10000000;
					}
				</script>
			<?php
		}
		
		/**
		 * User-defined error handler
		 *
		 * @param int $errno
		 * @param string $errstr
		 * @param string $errfile
		 * @param int $errline
		 * @param array $errcontext
		 */
		function saveError($errno, $errstr, $errfile = '', $errline = '', $errcontext = '')
		{
			//echo '<b>error</b> ['.$errno.'] = ['.$errstr.']<br>';
			$errorType = $this->getErrorNameByCode($errno);
			if(!$errorType)
			{
				trigger_error('Unknown error type ['.$errno.']', E_USER_ERROR);
				return false;
			}
			$this->Data[] = Array('no' => $errno, 'str' => $errstr, 'file' => $errfile, 'line' => $errline, 'context' => $errcontext, 'debug_type' => 'error');
			if( substr($errorType,0,5) == 'Fatal')
			{
				echo '<script language="javascript">var $isFatalError = 1;</script>';
				exit;
			}
		}
		
	}
	
	$debugger = new Debugger();
	$debugger->appendHTML('<a href="javascript:toggleDebugLayer();">Hide Debugger</a>');
	
	set_error_handler( array(&$debugger,'saveError')  );
	register_shutdown_function( array(&$debugger,'printReport') );
	
?>