<?php
/**
 * BitForgeNet Guest Tickets - Built-in ticket background painter.
 *
 * Draws a print-ready 100 x 250 mm ticket background at 300 dpi entirely with GD,
 * in any of the plugin's colour palettes. Used for the default template and for the
 * "create a colour variation" action so two events never ship an identical ticket.
 *
 * @package BitForgeNet_Guest_Tickets
 * @since   1.0.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * Paints ticket backgrounds.
 */
class BFNET_Template_Painter {

	/**
	 * Active palette.
	 *
	 * @var array
	 */
	private $palette;

	/**
	 * Canvas width.
	 *
	 * @var int
	 */
	private $width;

	/**
	 * Canvas height.
	 *
	 * @var int
	 */
	private $height;

	/**
	 * Constructor.
	 *
	 * @param array $palette Palette definition.
	 * @param int   $width   Canvas width in pixels.
	 * @param int   $height  Canvas height in pixels.
	 */
	public function __construct( array $palette, $width = BFNET_Template::WIDTH_PX, $height = BFNET_Template::HEIGHT_PX ) {
		$this->palette = wp_parse_args(
			$palette,
			array(
				'background' => '#0B0B0B',
				'panel'      => '#141414',
				'primary'    => '#C9A227',
				'highlight'  => '#F5E6A8',
				'text'       => '#FFFFFF',
				'muted'      => '#A8A8A8',
			)
		);
		$this->width   = (int) $width;
		$this->height  = (int) $height;
	}

	/**
	 * Render and persist the background and its preview.
	 *
	 * @return array|WP_Error ['background' => filename, 'preview' => filename]
	 */
	public function render() {
		$img = $this->paint();
		if ( ! $img ) {
			return new WP_Error( 'bfnet_painter', __( 'The ticket background could not be generated. Check that PHP GD is available with FreeType support.', 'bitforgenet-guest-tickets' ) );
		}

		$storage = BFNET_Install::storage();
		$dir     = $storage['dir'] . '/templates';

		if ( ! is_dir( $dir ) ) {
			wp_mkdir_p( $dir );
		}

		$stem       = 'bfnet-tpl-' . gmdate( 'Ymd' ) . '-' . wp_generate_password( 8, false, false );
		$background = $stem . '.png';
		$preview    = $stem . '-preview.jpg';

		imagepng( $img, $dir . '/' . $background, 6 );

		// Small JPEG preview for the admin UI.
		$preview_width  = 480;
		$preview_height = (int) round( $this->height * ( $preview_width / $this->width ) );
		$thumb          = imagecreatetruecolor( $preview_width, $preview_height );
		imagecopyresampled( $thumb, $img, 0, 0, 0, 0, $preview_width, $preview_height, $this->width, $this->height );
		imagejpeg( $thumb, $dir . '/' . $preview, 82 );

		imagedestroy( $thumb );
		imagedestroy( $img );

		return array(
			'background' => $background,
			'preview'    => $preview,
		);
	}

	/**
	 * Paint the background image.
	 *
	 * @return resource|GdImage|null
	 */
	public function paint() {
		$w = $this->width;
		$h = $this->height;

		$img = imagecreatetruecolor( $w, $h );
		if ( ! $img ) {
			return null;
		}

		imagealphablending( $img, true );
		imagesavealpha( $img, false );

		$p = $this->palette;

		// Base: a soft vertical gradient with a lighter mid panel.
		BFNET_Canvas::gradient_v( $img, array( 'x' => 0, 'y' => 0, 'w' => $w, 'h' => (int) round( $h * 0.45 ) ), $p['background'], $p['panel'] );
		BFNET_Canvas::gradient_v( $img, array( 'x' => 0, 'y' => (int) round( $h * 0.45 ), 'w' => $w, 'h' => $h - (int) round( $h * 0.45 ) ), $p['panel'], $p['background'] );

		$this->radial_glow( $img, (int) round( $w / 2 ), (int) round( $h * 0.16 ), (int) round( $w * 0.85 ), $p['primary'] );
		$this->diagonal_texture( $img );

		// Top and bottom metallic bands.
		BFNET_Canvas::gradient_v( $img, array( 'x' => 0, 'y' => 0, 'w' => $w, 'h' => 16 ), $p['highlight'], $p['primary'] );
		BFNET_Canvas::gradient_v( $img, array( 'x' => 0, 'y' => $h - 16, 'w' => $w, 'h' => 16 ), $p['primary'], $p['highlight'] );

		// Double ornamental frame.
		$this->frame( $img, 52, $p['primary'], 3 );
		$this->frame( $img, 68, $p['primary'], 1 );
		$this->corner_marks( $img, 52, 90, $p['highlight'] );

		// Section rules.
		foreach ( array( 930, 1140, 1400, 1780, 2460, 2670 ) as $y ) {
			if ( $y < $h - 80 ) {
				BFNET_Canvas::fading_rule( $img, 190, $y, $w - 380, 2, $p['primary'] );
			}
		}

		// Ticket stub perforation above the QR panel.
		$this->perforation( $img, 1782, $p['primary'] );

		// White panel behind the QR so any palette stays scannable.
		$this->qr_panel( $img, 336, 1800, 510, 510 );

		// Fine security texture in the bottom margin.
		$this->guilloche( $img );

		return $img;
	}

	/* --------------------------------------------------------------------- */
	/* Decorative primitives                                                 */
	/* --------------------------------------------------------------------- */

	/**
	 * Paint a soft radial glow.
	 *
	 * @param resource|GdImage $img    Image handle.
	 * @param int              $cx     Centre x.
	 * @param int              $cy     Centre y.
	 * @param int              $radius Radius.
	 * @param string           $hex    Colour.
	 * @return void
	 */
	private function radial_glow( $img, $cx, $cy, $radius, $hex ) {
		list( $r, $g, $b ) = BFNET_Canvas::rgb( $hex );
		$steps             = 22;

		// Each ring is almost fully transparent; the accumulation produces a soft halo.
		for ( $i = $steps; $i > 0; $i-- ) {
			$size  = (int) round( $radius * ( $i / $steps ) );
			$color = imagecolorallocatealpha( $img, $r, $g, $b, 125 );
			imagefilledellipse( $img, $cx, $cy, $size, (int) round( $size * 0.72 ), $color );
		}
	}

	/**
	 * Very subtle diagonal hatching for a textured paper feel.
	 *
	 * @param resource|GdImage $img Image handle.
	 * @return void
	 */
	private function diagonal_texture( $img ) {
		list( $r, $g, $b ) = BFNET_Canvas::rgb( $this->palette['text'] );
		$color             = imagecolorallocatealpha( $img, $r, $g, $b, 123 );

		for ( $x = -$this->height; $x < $this->width; $x += 26 ) {
			imageline( $img, $x, 0, $x + $this->height, $this->height, $color );
		}
	}

	/**
	 * Draw a rectangular frame inset from the edges.
	 *
	 * @param resource|GdImage $img       Image handle.
	 * @param int              $inset     Inset in pixels.
	 * @param string           $hex       Colour.
	 * @param int              $thickness Line thickness.
	 * @return void
	 */
	private function frame( $img, $inset, $hex, $thickness = 2 ) {
		list( $r, $g, $b ) = BFNET_Canvas::rgb( $hex );
		$color             = imagecolorallocatealpha( $img, $r, $g, $b, 40 );

		for ( $i = 0; $i < max( 1, $thickness ); $i++ ) {
			imagerectangle( $img, $inset + $i, $inset + $i, $this->width - $inset - 1 - $i, $this->height - $inset - 1 - $i, $color );
		}
	}

	/**
	 * Draw bright corner accents on the frame.
	 *
	 * @param resource|GdImage $img    Image handle.
	 * @param int              $inset  Frame inset.
	 * @param int              $length Accent length.
	 * @param string           $hex    Colour.
	 * @return void
	 */
	private function corner_marks( $img, $inset, $length, $hex ) {
		$color = BFNET_Canvas::color( $img, $hex, 10 );
		$x1    = $inset;
		$y1    = $inset;
		$x2    = $this->width - $inset - 1;
		$y2    = $this->height - $inset - 1;

		$segments = array(
			array( $x1, $y1, $x1 + $length, $y1 ),
			array( $x1, $y1, $x1, $y1 + $length ),
			array( $x2 - $length, $y1, $x2, $y1 ),
			array( $x2, $y1, $x2, $y1 + $length ),
			array( $x1, $y2 - $length, $x1, $y2 ),
			array( $x1, $y2, $x1 + $length, $y2 ),
			array( $x2 - $length, $y2, $x2, $y2 ),
			array( $x2, $y2 - $length, $x2, $y2 ),
		);

		foreach ( $segments as $segment ) {
			for ( $t = 0; $t < 4; $t++ ) {
				$dx = ( $segment[0] === $segment[2] ) ? $t : 0;
				$dy = ( $segment[1] === $segment[3] ) ? $t : 0;
				$dx = ( $segment[0] > $this->width / 2 ) ? -$dx : $dx;
				$dy = ( $segment[1] > $this->height / 2 ) ? -$dy : $dy;
				imageline( $img, $segment[0] + $dx, $segment[1] + $dy, $segment[2] + $dx, $segment[3] + $dy, $color );
			}
		}
	}

	/**
	 * Draw a dashed perforation line with edge notches.
	 *
	 * @param resource|GdImage $img Image handle.
	 * @param int              $y   Vertical position.
	 * @param string           $hex Colour.
	 * @return void
	 */
	private function perforation( $img, $y, $hex ) {
		$color      = BFNET_Canvas::color( $img, $hex, 55 );
		$background = BFNET_Canvas::color( $img, $this->palette['background'], 0 );

		for ( $x = 120; $x < $this->width - 120; $x += 28 ) {
			imagefilledrectangle( $img, $x, $y, $x + 14, $y + 3, $color );
		}

		// Half-circle notches biting into both edges.
		imagefilledellipse( $img, 0, $y + 2, 96, 96, $background );
		imagefilledellipse( $img, $this->width, $y + 2, 96, 96, $background );

		$edge = BFNET_Canvas::color( $img, $hex, 75 );
		imagearc( $img, 0, $y + 2, 96, 96, -90, 90, $edge );
		imagearc( $img, $this->width, $y + 2, 96, 96, 90, 270, $edge );
	}

	/**
	 * Draw the light panel that guarantees QR contrast.
	 *
	 * @param resource|GdImage $img Image handle.
	 * @param int              $x   Left.
	 * @param int              $y   Top.
	 * @param int              $w   Width.
	 * @param int              $h   Height.
	 * @return void
	 */
	private function qr_panel( $img, $x, $y, $w, $h ) {
		$shadow = imagecolorallocatealpha( $img, 0, 0, 0, 100 );
		BFNET_Canvas::rounded_rect( $img, $x + 6, $y + 8, $w, $h, 26, $shadow );

		$white = imagecolorallocate( $img, 255, 255, 255 );
		BFNET_Canvas::rounded_rect( $img, $x, $y, $w, $h, 24, $white );

		$accent = BFNET_Canvas::color( $img, $this->palette['primary'], 30 );
		imagerectangle( $img, $x - 3, $y - 3, $x + $w + 2, $y + $h + 2, $accent );
	}

	/**
	 * Guilloche-style interference lines in the bottom margin — a fine, hard-to-photocopy
	 * texture that also reads as a security feature on a printed ticket.
	 *
	 * @param resource|GdImage $img Image handle.
	 * @return void
	 */
	private function guilloche( $img ) {
		list( $r, $g, $b ) = BFNET_Canvas::rgb( $this->palette['primary'] );
		$color             = imagecolorallocatealpha( $img, $r, $g, $b, 116 );

		$left   = 96;
		$right  = $this->width - 96;
		$centre = (int) round( $this->height - 52 );

		for ( $phase = 0; $phase < 7; $phase++ ) {
			$previous = null;
			for ( $x = $left; $x <= $right; $x += 2 ) {
				$t = ( $x - $left ) / max( 1, $right - $left );
				$y = $centre
					+ (int) round( 13 * sin( ( $t * M_PI * 9 ) + ( $phase * 0.42 ) ) )
					+ (int) round( 6 * sin( ( $t * M_PI * 23 ) - ( $phase * 0.7 ) ) );
				if ( null !== $previous ) {
					imageline( $img, $previous[0], $previous[1], $x, $y, $color );
				}
				$previous = array( $x, $y );
			}
		}
	}
}
