<?php
/**
 * @package HaloMVC
 */
namespace HaloMVC;
/**
* first LL logger class
* Usage:
* $log = new ccLogger('/folder/', ccLogger::INFO ); // see log level threshhold values below
* $log->logInfo('Returned a million search results'); //Prints to the log file
* $log->logFatal('Oh dear.'); //Prints to the log file
* $log->logDebug('x = 5'); //Prints nothing due to current severity threshhold
*
*/
class Logger
{
    /**
* Error severity, from low to high. From BSD syslog RFC, secion 4.1.1
* @link http://www.faqs.org/rfcs/rfc3164.html
*/
    const EMERG = 0; // Emergency: system is unusable
    const ALERT = 1; // Alert: action must be taken immediately
    const CRIT = 2; // Critical: critical conditions
    const ERR = 3; // Error: error conditions
    const WARN = 4; // Warning: warning conditions
    const NOTICE = 5; // Notice: normal but significant condition
    const INFO = 6; // Informational: informational messages
    const DEBUG = 7; // Debug: debug messages

    //custom logging level
    /**
* Log nothing at all
*/
    const OFF = 8;
    /**
* Alias for CRIT
* @deprecated
*/
    const FATAL = 2;

    /**
* Internal status codes
*/
    const STATUS_LOG_OPEN = 1;
    const STATUS_OPEN_FAILED = 2;
    const STATUS_LOG_CLOSED = 3;

    /**
* Current status of the log file
* @var integer
*/
    private $_logStatus = self::STATUS_LOG_CLOSED;
    /**
* Holds messages generated by the class
* @var array
*/
    private $_messageQueue = array();
    /**
* Path to the log file
* @var string
*/
    private $_logFilePath = null;
    /**
* Current minimum logging threshold
* @var integer
*/
    private $_severityThreshold = self::INFO;
    /**
* This holds the file handle for this instance's log file
* @var resource
*/
    private $_fileHandle = null;

    /**
* Standard messages produced by the class. Can be modified for il8n
* @var array
*/
    private $_messages = array(
        //'writefail' => 'The file exists, but could not be opened for writing. Check that appropriate permissions have been set.',
        'writefail' => 'The file could not be written to. Check that appropriate permissions have been set.',
        'opensuccess' => 'The log file was opened successfully.',
        'openfail' => 'The file could not be opened. Check permissions.',
    );

    /**
* Default severity of log messages, if not specified
* @var integer
*/
    private static $_defaultSeverity = self::DEBUG;
    /**
* Valid PHP date() format string for log timestamps
* @var string
*/
    private static $_dateFormat = 'Y-m-d H:i:s.u';
    /**
* Octal notation for default permissions of the log file
* @var integer
*/
    private static $_defaultPermissions = 0777;
    /**
* Array of ccLogger instances, part of Singleton pattern
* @var array
*/
    private static $instances = array();

    /**
* Partially implements the Singleton pattern. Each $logDirectory gets one
* instance.
*
* @param string $logDirectory File path to the logging directory
* @param integer $severity One of the pre-defined severity constants
* @return ccLogger
*/
    public static function instance($logDirectory = false, $severity = false)
    {
        if ($severity === false) {
            $severity = self::$_defaultSeverity;
        }
        
        if ($logDirectory === false) {
            if (count(self::$instances) > 0) {
                return current(self::$instances);
            } else {
                $logDirectory = dirname(__FILE__);
            }
        }

        if (in_array($logDirectory, self::$instances)) {
            return self::$instances[$logDirectory];
        }

        self::$instances[$logDirectory] = new self($logDirectory, $severity);

        return self::$instances[$logDirectory];
    }

    /**
* Class constructor
*
* @param string $logDirectory File path to the logging directory
* @param integer $severity One of the pre-defined severity constants
* @return void
*/
    public function __construct($logDirectory, $severity = self::DEBUG)
    {
        $logDirectory = rtrim($logDirectory, '\\/');

        if ($severity === self::OFF) {
            return;
        }

        $this->_logFilePath = $logDirectory
            . DIRECTORY_SEPARATOR
            . 'ccLogger_'
            . date('Y-m-d')
            . '.log.txt';

        $this->_severityThreshold = $severity;
        if (!file_exists($logDirectory)) {
            mkdir($logDirectory, self::$_defaultPermissions, true);
        }

        if (file_exists($this->_logFilePath) && !is_writable($this->_logFilePath)) {
            $this->_logStatus = self::STATUS_OPEN_FAILED;
            $this->_messageQueue[] = $this->_messages['writefail'];
            return;
        }

        if (($this->_fileHandle = fopen($this->_logFilePath, 'a'))) {
            $this->_logStatus = self::STATUS_LOG_OPEN;
            $this->_messageQueue[] = $this->_messages['opensuccess'];
        } else {
            $this->_logStatus = self::STATUS_OPEN_FAILED;
            $this->_messageQueue[] = $this->_messages['openfail'];
        }
    }

    /**
* Class destructor
*/
    public function __destruct()
    {
        if ($this->_fileHandle) {
            fclose($this->_fileHandle);
        }
    }
    /**
* Writes a $line to the log with a severity level of DEBUG
*
* @param string $line Information to log
* @return void
*/
    public function logDebug($line)
    {
        $this->log($line, self::DEBUG);
    }

    /**
* Returns (and removes) the last message from the queue.
* @return string
*/
    public function getMessage()
    {
        return array_pop($this->_messageQueue);
    }

    /**
* Returns the entire message queue (leaving it intact)
* @return array
*/
    public function getMessages()
    {
        return $this->_messageQueue;
    }

    /**
* Empties the message queue
* @return void
*/
    public function clearMessages()
    {
        $this->_messageQueue = array();
    }

    /**
* Sets the date format used by all instances of ccLogger
*
* @param string $dateFormat Valid format string for date()
*/
    public static function setDateFormat($dateFormat)
    {
        self::$_dateFormat = $dateFormat;
    }

    /**
* Writes a $line to the log with a severity level of INFO. Any information
* can be used here, or it could be used with E_STRICT errors
*
* @param string $line Information to log
* @return void
*/
    public function logInfo($line)
    {
        $this->log($line, self::INFO);
    }

    /**
* Writes a $line to the log with a severity level of NOTICE. Generally
* corresponds to E_STRICT, E_NOTICE, or E_USER_NOTICE errors
*
* @param string $line Information to log
* @return void
*/
    public function logNotice($line)
    {
        $this->log($line, self::NOTICE);
    }

    /**
* Writes a $line to the log with a severity level of WARN. Generally
* corresponds to E_WARNING, E_USER_WARNING, E_CORE_WARNING, or
* E_COMPILE_WARNING
*
* @param string $line Information to log
* @return void
*/
    public function logWarn($line)
    {
        $this->log($line, self::WARN);
    }

    /**
* Writes a $line to the log with a severity level of ERR. Most likely used
* with E_RECOVERABLE_ERROR
*
* @param string $line Information to log
* @return void
*/
    public function logError($line)
    {
        $this->log($line, self::ERR);
    }

    /**
* Writes a $line to the log with a severity level of FATAL. Generally
* corresponds to E_ERROR, E_USER_ERROR, E_CORE_ERROR, or E_COMPILE_ERROR
*
* @param string $line Information to log
* @return void
* @deprecated Use logCrit
*/
    public function logFatal($line)
    {
        $this->log($line, self::FATAL);
    }

    /**
* Writes a $line to the log with a severity level of ALERT.
*
* @param string $line Information to log
* @return void
*/
    public function logAlert($line)
    {
        $this->log($line, self::ALERT);
    }

    /**
* Writes a $line to the log with a severity level of CRIT.
*
* @param string $line Information to log
* @return void
*/
    public function logCrit($line)
    {
        $this->log($line, self::CRIT);
    }

    /**
* Writes a $line to the log with a severity level of EMERG.
*
* @param string $line Information to log
* @return void
*/
    public function logEmerg($line)
    {
        $this->log($line, self::EMERG);
    }

    /**
* Writes a $line to the log with the given severity
*
* @param string $line Text to add to the log
* @param integer $severity Severity level of log message (use constants)
*/
    public function log($line, $severity = self::DEBUG)
    {
        if ($this->_severityThreshold >= $severity) {
            $status = $this->_getTimeLine($severity);
            $this->writeFreeFormLine("$status\n$line\n\n");
        }
    }

    /**
* Writes a line to the log without prepending a status or timestamp
*
* @param string $line Line to write to the log
* @return void
*/
    public function writeFreeFormLine($line)
    {
        if ($this->_logStatus == self::STATUS_LOG_OPEN
            && $this->_severityThreshold != self::OFF) {
            if (fwrite($this->_fileHandle, $line) === false) {
                $this->_messageQueue[] = $this->_messages['writefail'];
            }
        }
    }

    private function _getTimeLine($level)
    {
        $time = date(self::$_dateFormat);

        switch ($level) {
            case self::EMERG:
                return "$time - EMERG -->";
            case self::ALERT:
                return "$time - ALERT -->";
            case self::CRIT:
                return "$time - CRIT -->";
            case self::FATAL: # FATAL is an alias of CRIT
                return "$time - FATAL -->";
            case self::NOTICE:
                return "$time - NOTICE -->";
            case self::INFO:
                return "$time - INFO -->";
            case self::WARN:
                return "$time - WARN -->";
            case self::DEBUG:
                return "$time - DEBUG -->";
            case self::ERR:
                return "$time - ERROR -->";
            default:
                return "$time - LOG -->";
        }
    }
}