<?php
/* Copyright (C) 2025 Eduard Wisch <data@data-it-solution.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

/**
 * \file    lib/qrcode.class.php
 * \ingroup epcqr
 * \brief   QR-Code Generator mit lokalem Caching und Styling
 */

/**
 * QRCode Generator Klasse
 * Generiert QR-Codes mit optionalen Styling-Optionen (Farben, Logo)
 */
class QRCodeGenerator
{
	private $db;
	private $cacheDir;

	// Styling-Optionen
	private $fgColor = array(0, 0, 0);       // Vordergrundfarbe (schwarz)
	private $bgColor = array(255, 255, 255); // Hintergrundfarbe (weiß)
	private $bgTransparent = false;           // Transparenter Hintergrund
	private $logoPath = '';                   // Pfad zum Logo
	private $logoSize = 20;                   // Logo-Größe in Prozent (20% der QR-Code-Größe)
	private $pixelSize = 8;                   // Pixel-Größe für Module
	private $moduleStyle = 'square';          // Modul-Stil: square, rounded, dots, diamond
	private $borderStyle = 'none';            // Rahmen-Stil: none, simple, rounded, double, dashed
	private $borderWidth = 3;                 // Rahmenbreite in Pixeln
	private $borderPadding = 8;               // Abstand zwischen QR-Code und Rahmen

	/**
	 * Constructor
	 *
	 * @param DoliDB $db Database handler
	 */
	public function __construct($db)
	{
		global $conf;

		$this->db = $db;

		// Cache-Verzeichnis für QR-Codes
		$this->cacheDir = $conf->epcqr->dir_output.'/qrcodes';

		// Verzeichnis erstellen falls nicht vorhanden
		if (!is_dir($this->cacheDir)) {
			dol_mkdir($this->cacheDir);
		}

		// Styling aus Konfiguration laden
		$this->loadStyleFromConfig();
	}

	/**
	 * Lädt Styling-Einstellungen aus der Dolibarr-Konfiguration
	 */
	private function loadStyleFromConfig()
	{
		// Vordergrundfarbe (Hex -> RGB)
		$fgHex = getDolGlobalString('EPCQR_FG_COLOR', '#000000');
		$this->fgColor = $this->hexToRgb($fgHex);

		// Hintergrundfarbe (Hex -> RGB) oder transparent
		$bgHex = getDolGlobalString('EPCQR_BG_COLOR', '#FFFFFF');
		if (strtolower($bgHex) === 'transparent' || $bgHex === '') {
			$this->bgTransparent = true;
			$this->bgColor = array(255, 255, 255); // Fallback für Logo-Hintergrund
		} else {
			$this->bgTransparent = false;
			$this->bgColor = $this->hexToRgb($bgHex);
		}

		// Logo-Pfad
		$this->logoPath = getDolGlobalString('EPCQR_LOGO_PATH', '');

		// Logo-Größe (Prozent)
		$this->logoSize = getDolGlobalInt('EPCQR_LOGO_SIZE', 20);
		if ($this->logoSize < 5) $this->logoSize = 5;
		if ($this->logoSize > 30) $this->logoSize = 30; // Max 30% wegen Fehlerkorrektur

		// Modul-Stil
		$this->moduleStyle = getDolGlobalString('EPCQR_MODULE_STYLE', 'square');
		if (!in_array($this->moduleStyle, array('square', 'rounded', 'dots', 'diamond'))) {
			$this->moduleStyle = 'square';
		}

		// Rahmen-Stil
		$this->borderStyle = getDolGlobalString('EPCQR_BORDER_STYLE', 'none');
		if (!in_array($this->borderStyle, array('none', 'simple', 'rounded', 'double', 'dashed'))) {
			$this->borderStyle = 'none';
		}
	}

	/**
	 * Konvertiert Hex-Farbe zu RGB-Array
	 *
	 * @param  string  $hex  Hex-Farbcode (z.B. #FF0000)
	 * @return array         RGB-Array [r, g, b]
	 */
	private function hexToRgb($hex)
	{
		$hex = ltrim($hex, '#');

		if (strlen($hex) == 3) {
			$hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
		}

		return array(
			hexdec(substr($hex, 0, 2)),
			hexdec(substr($hex, 2, 2)),
			hexdec(substr($hex, 4, 2))
		);
	}

	/**
	 * Generiert einen EPC-QR-Code für SEPA-Überweisungen
	 *
	 * @param  string  $accountHolder  Kontoinhaber Name
	 * @param  string  $iban           IBAN
	 * @param  string  $bic            BIC (optional)
	 * @param  float   $amount         Betrag
	 * @param  string  $reference      Verwendungszweck
	 * @return string|false            Pfad zur generierten QR-Code-Datei oder false bei Fehler
	 */
	public function generateEPCQRCode($accountHolder, $iban, $bic, $amount, $reference)
	{
		// Eindeutigen Dateinamen generieren basierend auf Parametern UND Styling
		$styleHash = md5(implode('', $this->fgColor).implode('', $this->bgColor).($this->bgTransparent ? 'T' : 'F').$this->logoPath.$this->logoSize.$this->moduleStyle.$this->borderStyle);
		$hash = md5($accountHolder.$iban.$bic.$amount.$reference.$styleHash);
		$filename = 'epc_'.$hash.'.png';
		$filepath = $this->cacheDir.'/'.$filename;

		// Prüfen ob QR-Code bereits cached ist
		if (file_exists($filepath)) {
			dol_syslog("QRCodeGenerator: QR-Code aus Cache geladen: ".$filepath, LOG_DEBUG);
			return $filepath;
		}

		// QR-Code generieren
		$qrData = $this->generateEPCData($accountHolder, $iban, $bic, $amount, $reference);
		$result = $this->generateQRImage($qrData, $filepath);

		if ($result) {
			dol_syslog("QRCodeGenerator: QR-Code generiert: ".$filepath, LOG_DEBUG);
			return $filepath;
		}

		dol_syslog("QRCodeGenerator: Fehler beim Generieren des QR-Codes", LOG_ERR);
		return false;
	}

	/**
	 * Generiert einen generischen QR-Code aus beliebigem Text
	 *
	 * @param  string  $data           Daten für QR-Code
	 * @param  string  $prefix         Präfix für Dateinamen (default: 'qr')
	 * @return string|false            Pfad zur generierten QR-Code-Datei oder false bei Fehler
	 */
	public function generateQRCode($data, $prefix = 'qr')
	{
		// Eindeutigen Dateinamen generieren
		$styleHash = md5(implode('', $this->fgColor).implode('', $this->bgColor).($this->bgTransparent ? 'T' : 'F').$this->logoPath.$this->logoSize.$this->moduleStyle.$this->borderStyle);
		$hash = md5($data.$styleHash);
		$filename = $prefix.'_'.$hash.'.png';
		$filepath = $this->cacheDir.'/'.$filename;

		// Prüfen ob QR-Code bereits cached ist
		if (file_exists($filepath)) {
			dol_syslog("QRCodeGenerator: QR-Code aus Cache geladen: ".$filepath, LOG_DEBUG);
			return $filepath;
		}

		// QR-Code generieren
		$result = $this->generateQRImage($data, $filepath);

		if ($result) {
			dol_syslog("QRCodeGenerator: QR-Code generiert: ".$filepath, LOG_DEBUG);
			return $filepath;
		}

		dol_syslog("QRCodeGenerator: Fehler beim Generieren des QR-Codes", LOG_ERR);
		return false;
	}

	/**
	 * Generiert EPC-Datenstring für SEPA-QR-Codes
	 *
	 * @param  string  $accountHolder  Kontoinhaber Name
	 * @param  string  $iban           IBAN
	 * @param  string  $bic            BIC
	 * @param  float   $amount         Betrag
	 * @param  string  $reference      Verwendungszweck
	 * @return string                  EPC-Datenstring
	 */
	private function generateEPCData($accountHolder, $iban, $bic, $amount, $reference)
	{
		// EPC QR-Code Format (GiroCode Standard)
		$epcData = array(
			'BCD',                           // Service Tag
			'002',                           // Version
			'1',                             // Character set (1 = UTF-8)
			'SCT',                           // Identification (SEPA Credit Transfer)
			$bic,                            // BIC
			$accountHolder,                  // Empfänger Name
			$iban,                           // Empfänger IBAN
			'EUR'.number_format($amount, 2, '.', ''), // Betrag
			'',                              // Purpose (optional)
			$reference,                      // Verwendungszweck
			''                               // Beneficiary to originator information (optional)
		);

		return implode("\n", $epcData);
	}

	/**
	 * Generiert QR-Code-Bild aus Daten mit Styling
	 *
	 * @param  string  $data      Daten für QR-Code
	 * @param  string  $filepath  Zielpfad für PNG-Datei
	 * @return bool               true bei Erfolg, false bei Fehler
	 */
	private function generateQRImage($data, $filepath)
	{
		// TCPDF 2D Barcode Bibliothek laden
		require_once DOL_DOCUMENT_ROOT.'/includes/tecnickcom/tcpdf/tcpdf_barcodes_2d.php';

		try {
			// QR-Code mit hoher Fehlerkorrektur erstellen (H = 30% - notwendig für Logo)
			$errorLevel = !empty($this->logoPath) && file_exists($this->logoPath) ? 'H' : 'M';
			$barcodeobj = new TCPDF2DBarcode($data, 'QRCODE,'.$errorLevel);

			// PNG-Daten generieren
			$imageData = $barcodeobj->getBarcodePngData($this->pixelSize, $this->pixelSize, $this->fgColor);

			if ($imageData === false) {
				dol_syslog("QRCodeGenerator: TCPDF konnte kein PNG generieren", LOG_ERR);
				return false;
			}

			// Bild aus String erstellen für weitere Verarbeitung
			$image = imagecreatefromstring($imageData);

			if ($image === false) {
				// Fallback: Direkt speichern ohne Styling
				$result = file_put_contents($filepath, $imageData);
				return ($result !== false);
			}

			// Modul-Stil anwenden (rounded, dots, diamond) oder Standard-Verarbeitung
			if ($this->moduleStyle !== 'square') {
				$image = $this->applyModuleStyle($image);
			} else {
				// Standard: Hintergrund anwenden (transparent oder Farbe)
				if ($this->bgTransparent) {
					$image = $this->applyTransparentBackground($image);
				} elseif ($this->bgColor != array(255, 255, 255)) {
					$image = $this->applyBackgroundColor($image);
				}
			}

			// Logo hinzufügen (wenn konfiguriert)
			if (!empty($this->logoPath) && file_exists($this->logoPath)) {
				$image = $this->addLogo($image);
			}

			// Rahmen hinzufügen (wenn konfiguriert)
			if ($this->borderStyle !== 'none') {
				$image = $this->addBorder($image);
			}

			// Bild speichern
			$result = imagepng($image, $filepath);
			imagedestroy($image);

			return $result;
		} catch (Exception $e) {
			dol_syslog("QRCodeGenerator: Exception bei QR-Generierung: ".$e->getMessage(), LOG_ERR);
			return false;
		}
	}

	/**
	 * Wendet Hintergrundfarbe auf das Bild an
	 *
	 * @param  resource  $image  GD-Bild-Ressource
	 * @return resource          Bearbeitetes Bild
	 */
	private function applyBackgroundColor($image)
	{
		$width = imagesx($image);
		$height = imagesy($image);

		// Neues Bild mit Hintergrundfarbe erstellen
		$newImage = imagecreatetruecolor($width, $height);
		$bgColorAlloc = imagecolorallocate($newImage, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
		imagefill($newImage, 0, 0, $bgColorAlloc);

		// Vordergrundfarbe einmal allokieren
		$fgColorAlloc = imagecolorallocate($newImage, $this->fgColor[0], $this->fgColor[1], $this->fgColor[2]);

		// QR-Code-Module (dunkle Pixel) kopieren
		for ($x = 0; $x < $width; $x++) {
			for ($y = 0; $y < $height; $y++) {
				$rgb = imagecolorat($image, $x, $y);

				// Farbwerte extrahieren (funktioniert für indexed und truecolor)
				if (imageistruecolor($image)) {
					$r = ($rgb >> 16) & 0xFF;
					$g = ($rgb >> 8) & 0xFF;
					$b = $rgb & 0xFF;
				} else {
					$colors = imagecolorsforindex($image, $rgb);
					$r = $colors['red'];
					$g = $colors['green'];
					$b = $colors['blue'];
				}

				// Helligkeit berechnen (0 = schwarz, 255 = weiß)
				$brightness = ($r + $g + $b) / 3;

				// Dunkle Pixel (QR-Code-Module) = Vordergrundfarbe setzen
				if ($brightness < 128) {
					imagesetpixel($newImage, $x, $y, $fgColorAlloc);
				}
			}
		}

		imagedestroy($image);
		return $newImage;
	}

	/**
	 * Wendet transparenten Hintergrund auf das Bild an
	 *
	 * @param  resource  $image  GD-Bild-Ressource
	 * @return resource          Bearbeitetes Bild mit Transparenz
	 */
	private function applyTransparentBackground($image)
	{
		$width = imagesx($image);
		$height = imagesy($image);

		// Neues Bild mit Alpha-Kanal erstellen
		$newImage = imagecreatetruecolor($width, $height);

		// Alpha-Blending deaktivieren und Alpha speichern aktivieren
		imagealphablending($newImage, false);
		imagesavealpha($newImage, true);

		// Transparente Farbe erstellen und füllen
		$transparent = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
		imagefill($newImage, 0, 0, $transparent);

		// Für das Zeichnen Alpha-Blending aktivieren
		imagealphablending($newImage, true);

		// Vordergrundfarbe einmal allokieren
		$fgColorAlloc = imagecolorallocate($newImage, $this->fgColor[0], $this->fgColor[1], $this->fgColor[2]);

		// QR-Code-Module (dunkle Pixel) erkennen und kopieren
		// Verwendet Helligkeitswert statt exakte Farbprüfung für bessere Kompatibilität
		for ($x = 0; $x < $width; $x++) {
			for ($y = 0; $y < $height; $y++) {
				$rgb = imagecolorat($image, $x, $y);

				// Farbwerte extrahieren (funktioniert für indexed und truecolor)
				if (imageistruecolor($image)) {
					$r = ($rgb >> 16) & 0xFF;
					$g = ($rgb >> 8) & 0xFF;
					$b = $rgb & 0xFF;
				} else {
					$colors = imagecolorsforindex($image, $rgb);
					$r = $colors['red'];
					$g = $colors['green'];
					$b = $colors['blue'];
				}

				// Helligkeit berechnen (0 = schwarz, 255 = weiß)
				$brightness = ($r + $g + $b) / 3;

				// Dunkle Pixel (QR-Code-Module) = Helligkeit < 128
				// Helle Pixel (Hintergrund) = transparent lassen
				if ($brightness < 128) {
					imagesetpixel($newImage, $x, $y, $fgColorAlloc);
				}
			}
		}

		// Alpha speichern nochmal sicherstellen
		imagealphablending($newImage, false);
		imagesavealpha($newImage, true);

		imagedestroy($image);
		return $newImage;
	}

	/**
	 * Wendet Modul-Stil auf das QR-Code-Bild an (rounded, dots, diamond)
	 *
	 * @param  resource  $image  GD-Bild-Ressource
	 * @return resource          Bearbeitetes Bild mit gestylten Modulen
	 */
	private function applyModuleStyle($image)
	{
		$width = imagesx($image);
		$height = imagesy($image);

		// Modul-Matrix extrahieren (welche Module sind dunkel)
		$moduleMatrix = $this->extractModuleMatrix($image);
		$moduleCount = count($moduleMatrix);

		if ($moduleCount == 0) {
			return $image;
		}

		// Neue Bildgröße berechnen (etwas Padding hinzufügen)
		$moduleSize = $this->pixelSize;
		$newWidth = $moduleCount * $moduleSize;
		$newHeight = $moduleCount * $moduleSize;

		// Neues Bild erstellen
		$newImage = imagecreatetruecolor($newWidth, $newHeight);

		// Alpha für transparenten Hintergrund
		if ($this->bgTransparent) {
			imagealphablending($newImage, false);
			imagesavealpha($newImage, true);
			$bgColorAlloc = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
		} else {
			$bgColorAlloc = imagecolorallocate($newImage, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
		}
		imagefill($newImage, 0, 0, $bgColorAlloc);

		if ($this->bgTransparent) {
			imagealphablending($newImage, true);
		}

		// Vordergrundfarbe
		$fgColorAlloc = imagecolorallocate($newImage, $this->fgColor[0], $this->fgColor[1], $this->fgColor[2]);

		// Module zeichnen
		for ($row = 0; $row < $moduleCount; $row++) {
			for ($col = 0; $col < $moduleCount; $col++) {
				if (!empty($moduleMatrix[$row][$col])) {
					$x = $col * $moduleSize;
					$y = $row * $moduleSize;

					switch ($this->moduleStyle) {
						case 'rounded':
							$this->drawRoundedModule($newImage, $x, $y, $moduleSize, $fgColorAlloc);
							break;
						case 'dots':
							$this->drawDotModule($newImage, $x, $y, $moduleSize, $fgColorAlloc);
							break;
						case 'diamond':
							$this->drawDiamondModule($newImage, $x, $y, $moduleSize, $fgColorAlloc);
							break;
						default:
							imagefilledrectangle($newImage, $x, $y, $x + $moduleSize - 1, $y + $moduleSize - 1, $fgColorAlloc);
					}
				}
			}
		}

		if ($this->bgTransparent) {
			imagealphablending($newImage, false);
			imagesavealpha($newImage, true);
		}

		imagedestroy($image);
		return $newImage;
	}

	/**
	 * Extrahiert die Modul-Matrix aus dem QR-Code-Bild
	 *
	 * @param  resource  $image  GD-Bild-Ressource
	 * @return array             2D-Array mit true/false für jedes Modul
	 */
	private function extractModuleMatrix($image)
	{
		$width = imagesx($image);
		$height = imagesy($image);

		// Anzahl der Module berechnen
		$moduleCount = (int)($width / $this->pixelSize);
		$matrix = array();

		for ($row = 0; $row < $moduleCount; $row++) {
			$matrix[$row] = array();
			for ($col = 0; $col < $moduleCount; $col++) {
				// Mitte des Moduls samplen
				$x = ($col * $this->pixelSize) + (int)($this->pixelSize / 2);
				$y = ($row * $this->pixelSize) + (int)($this->pixelSize / 2);

				if ($x < $width && $y < $height) {
					$rgb = imagecolorat($image, $x, $y);

					if (imageistruecolor($image)) {
						$r = ($rgb >> 16) & 0xFF;
						$g = ($rgb >> 8) & 0xFF;
						$b = $rgb & 0xFF;
					} else {
						$colors = imagecolorsforindex($image, $rgb);
						$r = $colors['red'];
						$g = $colors['green'];
						$b = $colors['blue'];
					}

					$brightness = ($r + $g + $b) / 3;
					$matrix[$row][$col] = ($brightness < 128);
				} else {
					$matrix[$row][$col] = false;
				}
			}
		}

		return $matrix;
	}

	/**
	 * Zeichnet ein abgerundetes Modul
	 */
	private function drawRoundedModule($image, $x, $y, $size, $color)
	{
		$radius = (int)($size * 0.3); // 30% Radius für Rundung

		// Gefülltes Rechteck mit abgerundeten Ecken simulieren
		// Hauptrechteck (ohne Ecken)
		imagefilledrectangle($image, $x + $radius, $y, $x + $size - $radius - 1, $y + $size - 1, $color);
		imagefilledrectangle($image, $x, $y + $radius, $x + $size - 1, $y + $size - $radius - 1, $color);

		// Ecken als gefüllte Kreise
		imagefilledellipse($image, $x + $radius, $y + $radius, $radius * 2, $radius * 2, $color);
		imagefilledellipse($image, $x + $size - $radius - 1, $y + $radius, $radius * 2, $radius * 2, $color);
		imagefilledellipse($image, $x + $radius, $y + $size - $radius - 1, $radius * 2, $radius * 2, $color);
		imagefilledellipse($image, $x + $size - $radius - 1, $y + $size - $radius - 1, $radius * 2, $radius * 2, $color);
	}

	/**
	 * Zeichnet ein kreisförmiges Modul (Punkt)
	 */
	private function drawDotModule($image, $x, $y, $size, $color)
	{
		$centerX = $x + (int)($size / 2);
		$centerY = $y + (int)($size / 2);
		$diameter = $size - 2; // Etwas kleiner für Abstand

		imagefilledellipse($image, $centerX, $centerY, $diameter, $diameter, $color);
	}

	/**
	 * Zeichnet ein rautenförmiges Modul (Diamant)
	 */
	private function drawDiamondModule($image, $x, $y, $size, $color)
	{
		$centerX = $x + (int)($size / 2);
		$centerY = $y + (int)($size / 2);
		$half = (int)($size / 2) - 1;

		$points = array(
			$centerX, $y + 1,           // Oben
			$x + $size - 2, $centerY,   // Rechts
			$centerX, $y + $size - 2,   // Unten
			$x + 1, $centerY            // Links
		);

		imagefilledpolygon($image, $points, $color);
	}

	/**
	 * Fügt Logo in die Mitte des QR-Codes ein
	 *
	 * @param  resource  $image  GD-Bild-Ressource
	 * @return resource          Bearbeitetes Bild
	 */
	private function addLogo($image)
	{
		// Logo laden
		$logoInfo = getimagesize($this->logoPath);
		if ($logoInfo === false) {
			dol_syslog("QRCodeGenerator: Logo konnte nicht gelesen werden: ".$this->logoPath, LOG_WARNING);
			return $image;
		}

		$logoType = $logoInfo[2];
		$logo = null;

		switch ($logoType) {
			case IMAGETYPE_PNG:
				$logo = imagecreatefrompng($this->logoPath);
				break;
			case IMAGETYPE_JPEG:
				$logo = imagecreatefromjpeg($this->logoPath);
				break;
			case IMAGETYPE_GIF:
				$logo = imagecreatefromgif($this->logoPath);
				break;
			default:
				dol_syslog("QRCodeGenerator: Nicht unterstütztes Logo-Format", LOG_WARNING);
				return $image;
		}

		if ($logo === false) {
			return $image;
		}

		$qrWidth = imagesx($image);
		$qrHeight = imagesy($image);
		$logoOrigWidth = imagesx($logo);
		$logoOrigHeight = imagesy($logo);

		// Logo-Größe berechnen (Prozent der QR-Code-Größe)
		$logoNewWidth = (int)($qrWidth * $this->logoSize / 100);
		$logoNewHeight = (int)($logoOrigHeight * ($logoNewWidth / $logoOrigWidth));

		// Logo-Position (Mitte)
		$logoX = (int)(($qrWidth - $logoNewWidth) / 2);
		$logoY = (int)(($qrHeight - $logoNewHeight) / 2);

		// Hintergrund für Logo-Bereich (mit etwas Padding)
		// Nur wenn NICHT transparent - bei transparentem Hintergrund kein Rechteck zeichnen
		$padding = 4;
		if (!$this->bgTransparent) {
			$bgColorAlloc = imagecolorallocate($image, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
			imagefilledrectangle(
				$image,
				$logoX - $padding,
				$logoY - $padding,
				$logoX + $logoNewWidth + $padding,
				$logoY + $logoNewHeight + $padding,
				$bgColorAlloc
			);
		} else {
			// Bei transparentem Hintergrund: Bereich mit Transparenz füllen
			imagealphablending($image, false);
			$transparent = imagecolorallocatealpha($image, 0, 0, 0, 127);
			imagefilledrectangle(
				$image,
				$logoX - $padding,
				$logoY - $padding,
				$logoX + $logoNewWidth + $padding,
				$logoY + $logoNewHeight + $padding,
				$transparent
			);
			imagealphablending($image, true);
		}

		// Logo skaliert einfügen
		imagecopyresampled(
			$image,
			$logo,
			$logoX,
			$logoY,
			0,
			0,
			$logoNewWidth,
			$logoNewHeight,
			$logoOrigWidth,
			$logoOrigHeight
		);

		imagedestroy($logo);
		return $image;
	}

	/**
	 * Fügt einen Rahmen um den QR-Code hinzu
	 *
	 * @param  resource  $image  GD-Bild-Ressource
	 * @return resource          Bearbeitetes Bild mit Rahmen
	 */
	private function addBorder($image)
	{
		$origWidth = imagesx($image);
		$origHeight = imagesy($image);

		// Neue Bildgröße mit Padding und Rahmen
		$totalPadding = $this->borderPadding + $this->borderWidth;
		$newWidth = $origWidth + ($totalPadding * 2);
		$newHeight = $origHeight + ($totalPadding * 2);

		// Neues Bild erstellen
		$newImage = imagecreatetruecolor($newWidth, $newHeight);

		// Alpha für transparenten Hintergrund
		if ($this->bgTransparent) {
			imagealphablending($newImage, false);
			imagesavealpha($newImage, true);
			$bgColorAlloc = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
		} else {
			$bgColorAlloc = imagecolorallocate($newImage, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
		}
		imagefill($newImage, 0, 0, $bgColorAlloc);

		if ($this->bgTransparent) {
			imagealphablending($newImage, true);
		}

		// Rahmenfarbe (Vordergrundfarbe)
		$borderColor = imagecolorallocate($newImage, $this->fgColor[0], $this->fgColor[1], $this->fgColor[2]);

		// Rahmen zeichnen je nach Stil
		switch ($this->borderStyle) {
			case 'simple':
				$this->drawSimpleBorder($newImage, $newWidth, $newHeight, $borderColor);
				break;
			case 'rounded':
				$this->drawRoundedBorder($newImage, $newWidth, $newHeight, $borderColor);
				break;
			case 'double':
				$this->drawDoubleBorder($newImage, $newWidth, $newHeight, $borderColor);
				break;
			case 'dashed':
				$this->drawDashedBorder($newImage, $newWidth, $newHeight, $borderColor);
				break;
		}

		// Original-Bild in die Mitte kopieren
		imagecopy($newImage, $image, $totalPadding, $totalPadding, 0, 0, $origWidth, $origHeight);

		if ($this->bgTransparent) {
			imagealphablending($newImage, false);
			imagesavealpha($newImage, true);
		}

		imagedestroy($image);
		return $newImage;
	}

	/**
	 * Zeichnet einen einfachen Rahmen
	 */
	private function drawSimpleBorder($image, $width, $height, $color)
	{
		imagesetthickness($image, $this->borderWidth);
		$offset = (int)($this->borderWidth / 2);
		imagerectangle($image, $offset, $offset, $width - $offset - 1, $height - $offset - 1, $color);
	}

	/**
	 * Zeichnet einen abgerundeten Rahmen
	 */
	private function drawRoundedBorder($image, $width, $height, $color)
	{
		$radius = 15;
		$thickness = $this->borderWidth;

		// Dicke Linien für die geraden Seiten
		imagesetthickness($image, $thickness);
		$offset = (int)($thickness / 2);

		// Obere Linie (ohne Ecken)
		imageline($image, $radius, $offset, $width - $radius, $offset, $color);
		// Untere Linie
		imageline($image, $radius, $height - $offset - 1, $width - $radius, $height - $offset - 1, $color);
		// Linke Linie
		imageline($image, $offset, $radius, $offset, $height - $radius, $color);
		// Rechte Linie
		imageline($image, $width - $offset - 1, $radius, $width - $offset - 1, $height - $radius, $color);

		// Abgerundete Ecken als Bögen
		imagesetthickness($image, $thickness);

		// Oben-links
		imagearc($image, $radius, $radius, $radius * 2, $radius * 2, 180, 270, $color);
		// Oben-rechts
		imagearc($image, $width - $radius - 1, $radius, $radius * 2, $radius * 2, 270, 360, $color);
		// Unten-links
		imagearc($image, $radius, $height - $radius - 1, $radius * 2, $radius * 2, 90, 180, $color);
		// Unten-rechts
		imagearc($image, $width - $radius - 1, $height - $radius - 1, $radius * 2, $radius * 2, 0, 90, $color);
	}

	/**
	 * Zeichnet einen doppelten Rahmen
	 */
	private function drawDoubleBorder($image, $width, $height, $color)
	{
		$gap = 4; // Abstand zwischen den Linien
		imagesetthickness($image, 2);

		// Äußerer Rahmen
		imagerectangle($image, 1, 1, $width - 2, $height - 2, $color);
		// Innerer Rahmen
		imagerectangle($image, 1 + $gap, 1 + $gap, $width - 2 - $gap, $height - 2 - $gap, $color);
	}

	/**
	 * Zeichnet einen gestrichelten Rahmen
	 */
	private function drawDashedBorder($image, $width, $height, $color)
	{
		$dashLength = 8;
		$gapLength = 4;

		// Transparente Farbe für Lücken
		if ($this->bgTransparent) {
			$gapColor = imagecolorallocatealpha($image, 0, 0, 0, 127);
		} else {
			$gapColor = imagecolorallocate($image, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
		}

		// Strichmuster erstellen
		$style = array();
		for ($i = 0; $i < $dashLength; $i++) {
			$style[] = $color;
		}
		for ($i = 0; $i < $gapLength; $i++) {
			$style[] = $gapColor;
		}
		imagesetstyle($image, $style);

		imagesetthickness($image, $this->borderWidth);
		$offset = (int)($this->borderWidth / 2);

		// Rahmen mit Stil zeichnen
		imageline($image, $offset, $offset, $width - $offset, $offset, IMG_COLOR_STYLED);
		imageline($image, $width - $offset - 1, $offset, $width - $offset - 1, $height - $offset, IMG_COLOR_STYLED);
		imageline($image, $width - $offset, $height - $offset - 1, $offset, $height - $offset - 1, IMG_COLOR_STYLED);
		imageline($image, $offset, $height - $offset, $offset, $offset, IMG_COLOR_STYLED);
	}

	/**
	 * Löscht gecachte QR-Codes die älter als X Tage sind
	 *
	 * @param  int  $days  Anzahl Tage (default: 30)
	 * @return int         Anzahl gelöschter Dateien
	 */
	public function cleanCache($days = 30)
	{
		$deleted = 0;
		$threshold = time() - ($days * 86400);

		if (!is_dir($this->cacheDir)) {
			return 0;
		}

		$files = scandir($this->cacheDir);
		foreach ($files as $file) {
			if ($file === '.' || $file === '..') {
				continue;
			}

			$filepath = $this->cacheDir.'/'.$file;

			if (is_file($filepath) && filemtime($filepath) < $threshold) {
				if (unlink($filepath)) {
					$deleted++;
				}
			}
		}

		dol_syslog("QRCodeGenerator: ".$deleted." alte QR-Codes gelöscht", LOG_INFO);
		return $deleted;
	}

	/**
	 * Löscht alle gecachten QR-Codes (für Style-Änderungen)
	 *
	 * @return int  Anzahl gelöschter Dateien
	 */
	public function clearCache()
	{
		return $this->cleanCache(0);
	}
}
