<?php
namespace Veno\QrCdr\Lib;

/**
 * QR Code Reed-Solomon Item Class (Private Fork for Veno QrCdr)
 *
 * Implements Reed-Solomon error correction logic, Galois Field math,
 * and generator polynomial calculation.
 *
 * @package   QrCdr
 * @version   1.1.4-patched
 */

if ( ! defined( 'ABSPATH' ) && ! defined( 'QRCDR_OPEN_ACCESS' ) ) {
	exit;
}

class QrcdrRsItem {

	/**
	 * Bits per symbol.
	 * @var int
	 */
	public $mm;

	/**
	 * Symbols per block ((1 << mm) - 1).
	 * @var int
	 */
	public $nn;

	/**
	 * Alpha to polynomial lookup table.
	 * @var array
	 */
	public $alpha_to = array();

	/**
	 * Polynomial to alpha lookup table.
	 * @var array
	 */
	public $index_of = array();

	/**
	 * Generator polynomial.
	 * @var array
	 */
	public $genpoly = array();

	/**
	 * Number of generator roots (parity symbols).
	 * @var int
	 */
	public $nroots;

	/**
	 * First consecutive root index.
	 * @var int
	 */
	public $fcr;

	/**
	 * Primitive element index.
	 * @var int
	 */
	public $prim;

	/**
	 * Primitive root index.
	 * @var int
	 */
	public $iprim;

	/**
	 * Padding bytes.
	 * @var int
	 */
	public $pad;

	/**
	 * Galois Field polynomial.
	 * @var int
	 */
	public $gfpoly;

	/**
	 * Modulo nn arithmetic.
	 *
	 * @param int $value
	 * @return int
	 */
	public function modnn( $value ) {
		while ( $value >= $this->nn ) {
			$value -= $this->nn;
			$value  = ( $value >> $this->mm ) + ( $value & $this->nn );
		}

		return $value;
	}

	/**
	 * Initialize RS codec.
	 *
	 * @param int $symbol_size
	 * @param int $gf_poly
	 * @param int $fcr
	 * @param int $prim
	 * @param int $num_roots
	 * @param int $pad
	 * @return QrcdrRsItem|null
	 */
	public static function init_rs_char( $symbol_size, $gf_poly, $fcr, $prim, $num_roots, $pad ) {
		// Parameter validation
		if ( 0 > $symbol_size || 8 < $symbol_size ) {
			return null;
		}
		if ( 0 > $fcr || ( 1 << $symbol_size ) <= $fcr ) {
			return null;
		}
		if ( 0 >= $prim || ( 1 << $symbol_size ) <= $prim ) {
			return null;
		}
		if ( 0 > $num_roots || ( 1 << $symbol_size ) <= $num_roots ) {
			return null;
		}
		if ( 0 > $pad || ( ( 1 << $symbol_size ) - 1 - $num_roots ) <= $pad ) {
			return null;
		}

		$rs_instance         = new QrcdrRsItem();
		$rs_instance->mm     = $symbol_size;
		$rs_instance->nn     = ( 1 << $symbol_size ) - 1;
		$rs_instance->pad    = $pad;
		$rs_instance->gfpoly = $gf_poly;

		$rs_instance->alpha_to = array_fill( 0, $rs_instance->nn + 1, 0 );
		$rs_instance->index_of = array_fill( 0, $rs_instance->nn + 1, 0 );

		// Generate Galois Field tables
		$a0 = $rs_instance->nn;

		$rs_instance->index_of[0]  = $a0; // log(zero) = -infinity
		$rs_instance->alpha_to[ $a0 ] = 0; // alpha**-infinity = 0

		$shift_register = 1;
		for ( $i = 0; $i < $rs_instance->nn; $i++ ) {
			$rs_instance->index_of[ $shift_register ] = $i;
			$rs_instance->alpha_to[ $i ]                = $shift_register;
			$shift_register <<= 1;
			if ( $shift_register & ( 1 << $symbol_size ) ) {
				$shift_register ^= $gf_poly;
			}
			$shift_register &= $rs_instance->nn;
		}

		if ( 1 !== $shift_register ) {
			// Field generator polynomial is not primitive
			return null;
		}

		// Calculate generator polynomial from roots
		$rs_instance->genpoly = array_fill( 0, $num_roots + 1, 0 );
		$rs_instance->fcr     = $fcr;
		$rs_instance->prim    = $prim;
		$rs_instance->nroots  = $num_roots;

		for ( $iprim = 1; 0 !== ( $iprim % $prim ); $iprim += $rs_instance->nn ) {
			// Empty loop
		}
		$rs_instance->iprim = (int) ( $iprim / $prim );

		$rs_instance->genpoly[0] = 1;
		for ( $i = 0, $root = $fcr * $prim; $i < $num_roots; $i++, $root += $prim ) {
			$rs_instance->genpoly[ $i + 1 ] = 1;

			for ( $j = $i; $j > 0; $j-- ) {
				if ( 0 !== $rs_instance->genpoly[ $j ] ) {
					$rs_instance->genpoly[ $j ] = $rs_instance->genpoly[ $j - 1 ] ^ $rs_instance->alpha_to[ $rs_instance->modnn( $rs_instance->index_of[ $rs_instance->genpoly[ $j ] ] + $root ) ];
				} else {
					$rs_instance->genpoly[ $j ] = $rs_instance->genpoly[ $j - 1 ];
				}
			}
			$rs_instance->genpoly[0] = $rs_instance->alpha_to[ $rs_instance->modnn( $rs_instance->index_of[ $rs_instance->genpoly[0] ] + $root ) ];
		}

		// Convert genpoly to index form for faster encoding
		for ( $i = 0; $i <= $num_roots; $i++ ) {
			$rs_instance->genpoly[ $i ] = $rs_instance->index_of[ $rs_instance->genpoly[ $i ] ];
		}

		return $rs_instance;
	}

	/**
	 * Encode data block.
	 *
	 * @param array $data   Data array.
	 * @param array $parity Reference array for calculated parity.
	 * @return void
	 */
	public function encode_rs_char( $data, &$parity ) {
		$nn       =& $this->nn;
		$alpha_to =& $this->alpha_to;
		$index_of =& $this->index_of;
		$genpoly  =& $this->genpoly;
		$nroots   =& $this->nroots;
		$pad      =& $this->pad;
		$a0       = $nn;

		$parity = array_fill( 0, $nroots, 0 );

		for ( $i = 0; $i < ( $nn - $nroots - $pad ); $i++ ) {
			$feedback = $index_of[ $data[ $i ] ^ $parity[0] ];

			if ( $a0 !== $feedback ) {
				$feedback = $this->modnn( $nn - $genpoly[ $nroots ] + $feedback );

				for ( $j = 1; $j < $nroots; $j++ ) {
					$parity[ $j ] ^= $alpha_to[ $this->modnn( $feedback + $genpoly[ $nroots - $j ] ) ];
				}
			}

			array_shift( $parity );
			if ( $a0 !== $feedback ) {
				array_push( $parity, $alpha_to[ $this->modnn( $feedback + $genpoly[0] ) ] );
			} else {
				array_push( $parity, 0 );
			}
		}
	}
}
