<?php declare(strict_types=1);

require_once __DIR__ . '/../../common/QrbDateTime.php';

class QrbLogFile
{
    // (partial) file name of the log file
    private const LOG_FILE_NAME = "qr-bills.log";
    // full log file name including path
    private string $fullLogFileName;
    
    // Creates (or reuses an existing) log file.
    //
    // path: path to output directory
    public function __construct(string $path)
    {
        $timeStamp = QrbDateTime::createDateStamp(new DateTimeImmutable());
        $logFileName = self::LOG_FILE_NAME;
        $posDot = strrpos($logFileName, '.');

        if ($posDot !== false)    // '.' found
        {
            $currentLogFileName = substr_replace($logFileName, "_" . $timeStamp, $posDot, 0);
        }
        else
        {
            $currentLogFileName = $logFileName . "_" . $timeStamp;
        }
    
        $this->fullLogFileName = $path . "/" . $currentLogFileName;
    
        if (  (!file_exists($path))
           && (!mkdir($path, 0777, true)))
        {
            error_log("QrbLogFile: Unable to create path '{$path}'");
            return;
        }
            
        $this->deleteOldLogs($path, $logFileName, $currentLogFileName);
    }

    // deletes old log files (every other log file than the current one)
    private function deleteOldLogs(string $path, string $fileName, $currentFileName)
    {
        $posDot = strrpos($fileName, '.');
        if ($posDot !== false)    // '.' found
        {
            $fileName = substr($fileName, 0, $posDot);
        }
        
        if (($items = scandir($path)) === false)
        {
            error_log("QrbLogFile: Unable to scan directory '{$path}'");
            return;
        }
        
        foreach($items as $item)
        {
            if ((str_starts_with($item, $fileName)) && ($item != $currentFileName))
            {
                $fullFileName = $path . "/" . $item;
                 
                if (! unlink($fullFileName))
                {
                    error_log("QrbLogFile: Could not delete log file '{$fullFileName}'!");
                    return;
                }
            }
        }
    }
    
    // Writes the given message and optionally the additional lines to the log file.
    public function write(string $message, ?array $additionaLines = NULL) : int|false
    {
        return $this->writeFile($message, $additionaLines);
    }
    
    // Writes the given log entry into log file.    
    private function writeFile(string $message, ?array $additionaLines = NULL) : int|false
    {
        $logFileHandle = $this->openAndLock();
        if ($logFileHandle === false)   return false;
    
        try
        {
            $result = $this->writeAndFlush($logFileHandle, $message, true);            
            if (!$result)  return false;
            
            if ( $additionaLines && (count($additionaLines) > 0) )
            {
                $count = 1;
                
                foreach ($additionaLines as $line)
                {
                    $message = "\t[{$count}] {$line}";
                    
                    $result2 = $this->writeAndFlush($logFileHandle, $message, false);
                    if (!$result2)  return false;
                    
                    $result += $result2;
                    $count++;
                }
            }
        
            return $result;
        }
        catch (\Throwable $th)
        {
            error_log("QrbLogFile: Unexpected error => {$th}");
            return false;
        }
        finally
        {
            $this->closeAndUnlock($logFileHandle);
        }
    }
    
    // Opens and locks the log file.    
    private function openAndLock()
    {
        if (($logFileHandle = fopen($this->fullLogFileName, "a")) === false)
        {
            error_log("QrbLogFile: Unable to open file '{$this->fullLogFileName}'");
            return false;
        }

        if (!flock($logFileHandle, LOCK_EX))
        {
            error_log("QrbLogFile: Unable to lock file '{$this->fullLogFileName}'");
            return false;
        }

        return $logFileHandle;            
    }
    
    // Writes the given message to the log file and flushes the log file.    
    private function writeAndFlush($logFileHandle, string $message, bool $printDate) : int|false
    {
        $msg = $printDate ? QrbDateTime::createTimeStampForLog(new DateTimeImmutable()) 
                          : "";
        $msg .= $message . "\n";

        $result = fwrite($logFileHandle, $msg);

        if ($result === false)
        {
            error_log("QrbLogFile: Unable to write file '{$this->fullLogFileName}', msg: {$msg}");
        }
        else
        {
            if (!fflush($logFileHandle))
            {
                error_log("QrbLogFile: Unable to flush file '{$this->fullLogFileName}'");
                return false;
            }
        }
        
        return $result;
    }

    // Closes and unlocks the log file.    
    private function closeAndUnlock($logFileHandle) : void
    {
        if (!flock($logFileHandle, LOCK_UN))
            error_log("QrbLogFile: Unable to unlock file '{$this->fullLogFileName}'");
        
        if (!fclose($logFileHandle))
            error_log("QrbLogFile: Unable to close file '{$this->fullLogFileName}'");
    }
}

?>
