<?php class Log {

    const LOGLEVEL_DEBUG=1;
    const LOGLEVEL_INFO=2;
    const LOGLEVEL_WARN=3;
    const LOGLEVEL_ERROR=4;

    const LOG_LABELS=array(
        Log::LOGLEVEL_DEBUG=>'DEBUG',
        Log::LOGLEVEL_INFO =>'INFO ',
        Log::LOGLEVEL_WARN =>'WARN ',
        Log::LOGLEVEL_ERROR=>'ERROR',
    );

    private static int $logLevel;
    private static mixed $logFileHandle;
    private static bool $inited=false;

    public static function getLogLevels(): array {
        return [
            [ 'name'=>'Debug', 'color'=>'999', 'severity'=>self::LOGLEVEL_DEBUG ],
            [ 'name'=>'Info', 'color'=>'090', 'severity'=>self::LOGLEVEL_INFO ],
            [ 'name'=>'Warning', 'color'=>'990', 'severity'=>self::LOGLEVEL_WARN ],
            [ 'name'=>'Error', 'color'=>'900', 'severity'=>self::LOGLEVEL_ERROR ]
        ];
    }

    /**
     * Initiates logging. DOES NOT handle log rotation; must be handled by caller or otherwise.
     * @param string|null $logFileName The file to append to (creating if needed), or empty to echo logs to output. If
     *          specified, is written in the IceBear log directory (/var/log/icebear by default).
     * @param int $logLevel One of the constants Log::LOGLEVEL_DEBUG, Log::LOGLEVEL_INFO, Log::LOGLEVEL_WARN, Log::LOGLEVEL_ERROR.
     * @throws ServerException if the log file cannot be opened for appending.
     * @throws BadRequestException
     */
    public static function init(int $logLevel, string $logFileName=null): void {
        if(empty($logFileName)) {
			static::$logFileHandle=null;
		} else {
            if(str_starts_with($logFileName, '/')){
                $logFilePath=$logFileName;
            } else {
                if(!preg_match('/^[A-Za-z0-9._-]+$/',$logFileName)){
                    throw new ServerException("Bad log filename $logFileName in Log::init()");
                }
                $logDirectory=rtrim(static::getLogDirectory(),'/').'/';
                $logFilePath=$logDirectory.$logFileName;
            }
            static::$logFileHandle=@fopen($logFilePath,'a');
            if(!static::$logFileHandle){
                throw new ServerException('Could not open log file '.$logFilePath.' for appending.');
            }
        }
		static::setLogLevel($logLevel);
        static::$inited=true;
    }

    public static function getLogLevel(): int {
        return static::$logLevel;
    }

    public static function setLogLevel(int $logLevel): int {
        static::$logLevel=$logLevel;
        return static::$logLevel;
    }

    public static function isInited(): bool {
        return static::$inited;
    }

    /**
     * Writes the supplied message to the log, with level and GMT date/time, if the log level is equal to or higher than that specified in init().
     * @param int $logLevel The log level for this message.
     * @param string $message The message to log
     * @throws ServerException
     */
    public static function write(int $logLevel, string $message): void {
        if(!static::$inited){ throw new ServerException('Log not init()ed.'); }
        if($logLevel<static::$logLevel){ return; }
        $labels=static::LOG_LABELS;
        $out=$labels[$logLevel].' '.gmdate('d M y H:i:s').' '.$message."\n";
        if(static::$logFileHandle){
            fwrite(static::$logFileHandle, $out);
            fflush(static::$logFileHandle);
        } else {
            echo $out;
        }
    }

	/**
	 * @param string $message
	 * @throws ServerException
	 */
    public static function debug(string $message): void {
        Log::write(Log::LOGLEVEL_DEBUG, $message);
    }

	/**
	 * @param string $message
	 * @throws ServerException
	 */
    public static function info(string $message): void {
        Log::write(Log::LOGLEVEL_INFO, $message);
    }

	/**
	 * @param string $message
	 * @throws ServerException
	 */
    public static function warning(string $message): void {
        Log::write(Log::LOGLEVEL_WARN, $message);
    }

	/**
	 * @param string $message
	 * @throws ServerException
	 */
    public static function warn(string $message): void {
        Log::write(Log::LOGLEVEL_WARN, $message);
    }

    /**
     * @param $message
     * @throws ServerException
     */
    public static function error($message): void {
        Log::write(Log::LOGLEVEL_ERROR, $message);
    }


    /**
     * Tidies up, closing the log file handle.
     */
    public static function end(): void {
        static::$inited=false;
        if(static::$logFileHandle){
            fflush(static::$logFileHandle);
            @fclose(static::$logFileHandle);
            static::$logFileHandle=null;
        }
    }

    public static function getFieldValidations(): array {
        return array();
    }

    public static function getFieldHelpTexts(): array {
        return array();
    }

    /**
     * @return string
     * @throws BadRequestException
     */
    private static function getLogDirectory(): string {
        $logDirectory='/var/log/icebear/';
        if(!file_exists($logDirectory)){
            $logDirectory='C:/icebearlogs/'; //For dev on Windows
        }
        if(!file_exists($logDirectory)){
            throw new BadRequestException('IceBear log directory does not exist: '.$logDirectory);
        }
        return $logDirectory;
    }

    /**
     * Returns a list of files in the logs directory.
     * @return array
     * @throws BadRequestException if the log directory does not exist
     * @throws ForbiddenException if the current user is not an administrator.
     */
    public static function getAll(): array {
        if(!session::isAdmin()){
            throw new ForbiddenException('You must be an administrator to view logs');
        }
        $files=array();
        $sourceDir=static::getLogDirectory();
        $dir = dir($sourceDir);
        while (false !== ($filename = $dir->read())) {
            if('.'===$filename || '..'===$filename){ continue; }
            $files[]=array(
                'name'=>$filename,
                'size'=>filesize($sourceDir.$filename),
                'modified'=>date('Y-m-d h:i:s', filemtime($sourceDir.$filename)),
            );
        }
        return array(
            'total'=>count($files),
            'rows'=>$files
        );
    }

	/**
	 * Returns the contents of the specified log file, as an array of lines.
	 * @param string $name
	 * @return array[]
	 * @throws BadRequestException if the log directory does not exist
	 * @throws ForbiddenException if the current user is not an administrator.
	 * @throws NotFoundException if the file does not exist in the log directory
	 */
    public static function getByName(string $name): array {
        if(!session::isAdmin()){
            throw new ForbiddenException('You must be an administrator to view logs');
        }
        $files=static::getAll();
        $files=$files['rows'];
        $files=array_column($files, 'name');
        if(!in_array($name, $files)){
            throw new NotFoundException('That log file does not exist.');
        }
        $sourceDir=static::getLogDirectory();
        $fh=fopen($sourceDir.$name, 'r');
        $lines=array();
        $line=fgets($fh);
        while(false!==$line){
            $lines[]=$line;
            $line=fgets($fh);
        }
        return array('lines'=>$lines);
    }

	/**
	 * @param string $key
	 * @param string $value
	 * @return array[]
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 */
    public static function getByProperty(string $key, string $value): array {
        if('name'!==$key){
            throw new BadRequestException('Only name is recognised by Log::getByProperty');
        }
        return static::getByName($value);
    }

} 