<?php
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *	   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package log4php
 */

/**
 * The internal representation of logging event.
 *
 * @version $Revision: 1222216 $
 * @package log4php
 */
class LoggerLoggingEvent {

	private static $startTime;

	/** 
	* @var string Fully Qualified Class Name of the calling category class.
	*/
	private $fqcn;
	
	/**
	* @var Logger reference
	*/
	private $logger = null;
	
	/** 
	 * The category (logger) name.
	 * This field will be marked as private in future
	 * releases. Please do not access it directly. 
	 * Use the {@link getLoggerName()} method instead.
	 * @deprecated 
	 */
	private $categoryName;
	
	/** 
	 * Level of the logging event.
	 * @var LoggerLevel
	 */
	protected $level;
	
	/** 
	 * The nested diagnostic context (NDC) of logging event.
	 * @var string  
	 */
	private $ndc;
	
	/** 
	 * Have we tried to do an NDC lookup? If we did, there is no need
	 * to do it again.	Note that its value is always false when
	 * serialized. Thus, a receiving SocketNode will never use it's own
	 * (incorrect) NDC. See also writeObject method.
	 * @var boolean
	 */
	private $ndcLookupRequired = true;
	
	/** 
	 * Have we tried to do an MDC lookup? If we did, there is no need
	 * to do it again.	Note that its value is always false when
	 * serialized. See also the getMDC and getMDCCopy methods.
	 * @var boolean	 
	 */
	private $mdcCopyLookupRequired = true;
	
	/** 
	 * @var mixed The application supplied message of logging event. 
	 */
	private $message;
	
	/** 
	 * The application supplied message rendered through the log4php
	 * objet rendering mechanism. At present renderedMessage == message.
	 * @var string
	 */
	private $renderedMessage = null;
	
	/** 
	 * The name of thread in which this logging event was generated.
	 * log4php saves here the process id via {@link PHP_MANUAL#getmypid getmypid()} 
	 * @var mixed
	 */
	private $threadName = null;
	
	/** 
	* The number of seconds elapsed from 1/1/1970 until logging event
	* was created plus microseconds if available.
	* @var float
	*/
	public $timeStamp;
	
	/** 
	* @var LoggerLocationInfo Location information for the caller. 
	*/
	private $locationInfo = null;
	
	/**
	 * @var LoggerThrowableInformation log4php internal representation of throwable
	 */
	private $throwableInfo = null;
	
	/**
	* Instantiate a LoggingEvent from the supplied parameters.
	*
	* Except {@link $timeStamp} all the other fields of
	* LoggerLoggingEvent are filled when actually needed.
	*
	* @param string $fqcn name of the caller class.
	* @param mixed $logger The {@link Logger} category of this event or the logger name.
	* @param LoggerLevel $priority The level of this event.
	* @param mixed $message The message of this event.
	* @param integer $timeStamp the timestamp of this logging event.
	* @param Exception $throwable The throwable associated with logging event
	*/
	public function __construct($fqcn, $logger, $priority, $message, $timeStamp = null, Exception $throwable = null) {
		$this->fqcn = $fqcn;
		if($logger instanceof Logger) {
			$this->logger = $logger;
			$this->categoryName = $logger->getName();
		} else {
			$this->categoryName = strval($logger);
		}
		$this->level = $priority;
		$this->message = $message;
		if($timeStamp !== null && is_float($timeStamp)) {
			$this->timeStamp = $timeStamp;
		} else {
			$this->timeStamp = microtime(true);
		}
		
		if ($throwable !== null && $throwable instanceof Exception) {
			$this->throwableInfo = new LoggerThrowableInformation($throwable);
		}
	}

	/**
	 * Returns the full qualified classname.
	 * TODO: PHP does contain namespaces in 5.3. Those should be returned too, 
	 */
	 public function getFullQualifiedClassname() {
		 return $this->fqcn;
	 }
	 
	/**
	 * Set the location information for this logging event. The collected
	 * information is cached for future use.
	 *
	 * <p>This method uses {@link PHP_MANUAL#debug_backtrace debug_backtrace()} function (if exists)
	 * to collect informations about caller.</p>
	 * <p>It only recognize informations generated by {@link Logger} and its subclasses.</p>
	 * @return LoggerLocationInfo
	 */
	public function getLocationInformation() {
		if($this->locationInfo === null) {

			$locationInfo = array();
			$trace = debug_backtrace();
			$prevHop = null;
			// make a downsearch to identify the caller
			$hop = array_pop($trace);
			while($hop !== null) {
				if(isset($hop['class'])) {
					// we are sometimes in functions = no class available: avoid php warning here
					$className = strtolower($hop['class']);
					if(!empty($className) and ($className == 'logger' or 
						strtolower(get_parent_class($className)) == 'logger')) {
						$locationInfo['line'] = $hop['line'];
						$locationInfo['file'] = $hop['file'];
						break;
					}
				}
				$prevHop = $hop;
				$hop = array_pop($trace);
			}
			$locationInfo['class'] = isset($prevHop['class']) ? $prevHop['class'] : 'main';
			if(isset($prevHop['function']) and
				$prevHop['function'] !== 'include' and
				$prevHop['function'] !== 'include_once' and
				$prevHop['function'] !== 'require' and
				$prevHop['function'] !== 'require_once') {

				$locationInfo['function'] = $prevHop['function'];
			} else {
				$locationInfo['function'] = 'main';
			}
					 
			$this->locationInfo = new LoggerLocationInfo($locationInfo, $this->fqcn);
		}
		return $this->locationInfo;
	}

	/**
	 * Return the level of this event. Use this form instead of directly
	 * accessing the {@link $level} field.
	 * @return LoggerLevel	
	 */
	public function getLevel() {
		return $this->level;
	}

	/**
	 * Return the name of the logger. Use this form instead of directly
	 * accessing the {@link $categoryName} field.
	 * @return string  
	 */
	public function getLoggerName() {
		return $this->categoryName;
	}

	/**
	 * Return the message for this logging event.
	 *
	 * <p>Before serialization, the returned object is the message
	 * passed by the user to generate the logging event. After
	 * serialization, the returned value equals the String form of the
	 * message possibly after object rendering.
	 * @return mixed
	 */
	public function getMessage() {
		if($this->message !== null) {
			return $this->message;
		} else {
			return $this->getRenderedMessage();
		}
	}

	/**
	 * This method returns the NDC for this event. It will return the
	 * correct content even if the event was generated in a different
	 * thread or even on a different machine. The {@link LoggerNDC::get()} method
	 * should <b>never</b> be called directly.
	 * @return string  
	 */
	public function getNDC() {
		if($this->ndcLookupRequired) {
			$this->ndcLookupRequired = false;
			$this->ndc = LoggerNDC::get();
		}
		return $this->ndc;
	}

	/**
	 * Returns the the context corresponding to the <code>key</code>
	 * parameter.
	 * @return string
	 */
	public function getMDC($key) {
		return LoggerMDC::get($key);
	}
	
	/**
	 * Returns the entire MDC context.
	 * @return array
	 */
	public function getMDCMap () {
		return LoggerMDC::getMap();
	}

	/**
	 * Render message.
	 * @return string
	 */
	public function getRenderedMessage() {
		if($this->renderedMessage === null and $this->message !== null) {
			if(is_string($this->message)) {
				$this->renderedMessage = $this->message;
			} else {
				$rendererMap = Logger::getHierarchy()->getRendererMap();
				$this->renderedMessage= $rendererMap->findAndRender($this->message);
			}
		}
		return $this->renderedMessage;
	}

	/**
	 * Returns the time when the application started, as a UNIX timestamp 
	 * with microseconds.
	 * @return float
	 */
	public static function getStartTime() {
		if(!isset(self::$startTime)) {
			self::$startTime = microtime(true);
		}
		return self::$startTime; 
	}

	/**
	 * @return float
	 */
	public function getTimeStamp() {
		return $this->timeStamp;
	}
	
	/**
	 * Calculates the time of this event.
	 * @return the time after event starttime when this event has occured
	 */
	public function getTime() {
		$eventTime = $this->getTimeStamp();
		$eventStartTime = LoggerLoggingEvent::getStartTime();
		return number_format(($eventTime - $eventStartTime) * 1000, 0, '', '');
	}
	
	/**
	 * @return mixed
	 */
	public function getThreadName() {
		if ($this->threadName === null) {
			$this->threadName = (string)getmypid();
		}
		return $this->threadName;
	}

	/**
	 * @return mixed LoggerThrowableInformation
	 */
	public function getThrowableInformation() {
		return $this->throwableInfo;
	}
	
	/**
	 * Serialize this object
	 * @return string
	 */
	public function toString() {
		serialize($this);
	}
	
	/**
	 * Avoid serialization of the {@link $logger} object
	 */
	public function __sleep() {
		return array(
			'fqcn',
			'categoryName',
			'level',
			'ndc',
			'ndcLookupRequired',
			'message',
			'renderedMessage',
			'threadName',
			'timeStamp',
			'locationInfo',
		);
	}

}

LoggerLoggingEvent::getStartTime();
