<?php
namespace Veno\QrCdr\Lib;

use Exception;

/**
 * QR Code Raw Data Manager (Private Fork for Veno QrCdr)
 *
 * Handles raw data stream and Reed-Solomon Error Correction Code (ECC) calculation.
 *
 * @package   QrCdr
 * @version   1.1.4-patched
 */

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

class QrcdrRawCode {

	/**
	 * QR Code version.
	 * @var int
	 */
	public $version;

	/**
	 * Data codewords.
	 * @var array
	 */
	public $datacode = array();

	/**
	 * ECC codewords.
	 * @var array
	 */
	public $ecccode = array();

	/**
	 * Total RS blocks.
	 * @var int
	 */
	public $blocks;

	/**
	 * RS blocks array.
	 * @var QrcdrRsBlock[]
	 */
	public $rsblocks = array();

	/**
	 * Counter for getCode().
	 * @var int
	 */
	public $count;

	/**
	 * Data length.
	 * @var int
	 */
	public $dataLength;

	/**
	 * ECC length.
	 * @var int
	 */
	public $eccLength;

	/**
	 * Number of blocks in group 1.
	 * @var int
	 */
	public $b1;

	/**
	 * Constructor.
	 *
	 * @param QrcdrInput $input Input data.
	 * @throws Exception
	 */
	public function __construct( QrcdrInput $input ) {
		$ecc_spec = array( 0, 0, 0, 0, 0 );

		$this->datacode = $input->getByteStream();
		if ( null === $this->datacode ) {
			throw new Exception( 'Input string is null.' );
		}

		QrcdrSpec::getEccSpec( $input->getVersion(), $input->getErrorCorrectionLevel(), $ecc_spec );

		$this->version    = $input->getVersion();
		$this->b1         = QrcdrSpec::rsBlockNum1( $ecc_spec );
		$this->dataLength = QrcdrSpec::rsDataLength( $ecc_spec );
		$this->eccLength  = QrcdrSpec::rsEccLength( $ecc_spec );
		$this->ecccode    = array_fill( 0, $this->eccLength, 0 );
		$this->blocks     = QrcdrSpec::rsBlockNum( $ecc_spec );

		$return_code = $this->init( $ecc_spec );
		if ( 0 > $return_code ) {
			throw new Exception( 'Failed to initialize RS blocks.' );
		}

		$this->count = 0;
	}

	/**
	 * Initialize RS blocks.
	 *
	 * @param array $ecc_spec ECC specifications.
	 * @return int 0 on success.
	 */
	public function init( array $ecc_spec ) {
		// Block Group 1
		$data_length = QrcdrSpec::rsDataCodes1( $ecc_spec );
		$ecc_length  = QrcdrSpec::rsEccCodes1( $ecc_spec );
		$rs_encoder  = QrcdrRs::init_rs( 8, 0x11d, 0, 1, $ecc_length, 255 - $data_length - $ecc_length );

		$block_number  = 0;
		$data_position = 0;
		$ecc_position  = 0;
		for ( $i = 0; $i < QrcdrSpec::rsBlockNum1( $ecc_spec ); $i++ ) {
			$ecc = array_slice( $this->ecccode, $ecc_position );
			$this->rsblocks[ $block_number ] = new QrcdrRsBlock( $data_length, array_slice( $this->datacode, $data_position ), $ecc_length, $ecc, $rs_encoder );
			$this->ecccode = array_merge( array_slice( $this->ecccode, 0, $ecc_position ), $ecc );

			$data_position += $data_length;
			$ecc_position  += $ecc_length;
			$block_number++;
		}

		if ( 0 === QrcdrSpec::rsBlockNum2( $ecc_spec ) ) {
			return 0;
		}

		// Block Group 2
		$data_length = QrcdrSpec::rsDataCodes2( $ecc_spec );
		$ecc_length  = QrcdrSpec::rsEccCodes2( $ecc_spec );
		$rs_encoder  = QrcdrRs::init_rs( 8, 0x11d, 0, 1, $ecc_length, 255 - $data_length - $ecc_length );

		if ( null === $rs_encoder ) {
			return -1;
		}

		for ( $i = 0; $i < QrcdrSpec::rsBlockNum2( $ecc_spec ); $i++ ) {
			$ecc = array_slice( $this->ecccode, $ecc_position );
			$this->rsblocks[ $block_number ] = new QrcdrRsBlock( $data_length, array_slice( $this->datacode, $data_position ), $ecc_length, $ecc, $rs_encoder );
			$this->ecccode = array_merge( array_slice( $this->ecccode, 0, $ecc_position ), $ecc );

			$data_position += $data_length;
			$ecc_position  += $ecc_length;
			$block_number++;
		}

		return 0;
	}

	/**
	 * Get next codeword (interleaved).
	 *
	 * @return int Codeword or 0 if end.
	 */
	public function getCode() {
		$codeword;

		if ( $this->count < $this->dataLength ) {
			// Data codewords
			$row = (int) ( $this->count % $this->blocks );
			$col = (int) ( $this->count / $this->blocks );
			if ( $this->rsblocks[0]->dataLength <= $col ) {
				$row += $this->b1;
			}
			$codeword = $this->rsblocks[ $row ]->data[ $col ];
		} elseif ( $this->count < $this->dataLength + $this->eccLength ) {
			// ECC codewords
			$row = (int) ( ( $this->count - $this->dataLength ) % $this->blocks );
			$col = (int) ( ( $this->count - $this->dataLength ) / $this->blocks );
			$codeword = $this->rsblocks[ $row ]->ecc[ $col ];
		} else {
			return 0;
		}

		$this->count++;
		return $codeword;
	}
}
