<?php
namespace Veno\QrCdr\Lib;

/**
 * QR Code Bitstream Class (Private Fork for Veno QrCdr)
 *
 * Manages a stream of bits. Provides methods to allocate, append,
 * and convert numerical/byte data into a bit array.
 *
 * @package   QrCdr
 * @version   1.1.4-patched
 */

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

class QrcdrBitstream {

	/**
	 * The array of bits (0 or 1).
	 * @var array
	 */
	public $data = array();

	/**
	 * Return the number of bits in the stream.
	 *
	 * @return int Stream size.
	 */
	public function size() {
		return count( $this->data );
	}

	/**
	 * Allocate and initialize the bitstream with zeros.
	 *
	 * @param int $set_length Desired length.
	 * @return int Always 0.
	 */
	public function allocate( $set_length ) {
		$this->data = array_fill( 0, $set_length, 0 );
		return 0;
	}

	/**
	 * Create a new QrcdrBitstream from an integer.
	 *
	 * @param int $bits Number of bits to use.
	 * @param int $num  The integer to convert.
	 * @return self New QrcdrBitstream instance.
	 */
	public static function newFromNum( $bits, $num ) {
		$b_stream = new QrcdrBitstream();
		$b_stream->allocate( $bits );

		$mask = 1 << ( $bits - 1 );
		for ( $i = 0; $i < $bits; $i++ ) {
			if ( $num & $mask ) {
				$b_stream->data[ $i ] = 1;
			} else {
				$b_stream->data[ $i ] = 0;
			}
			$mask = $mask >> 1;
		}

		return $b_stream;
	}

	/**
	 * Create a new QrcdrBitstream from a byte array.
	 *
	 * @param int   $size Size of the byte array.
	 * @param array $data The byte array.
	 * @return self New QrcdrBitstream instance.
	 */
	public static function newFromBytes( $size, $data ) {
		$b_stream = new QrcdrBitstream();
		$b_stream->allocate( $size * 8 );
		$bit_pos = 0;

		for ( $i = 0; $i < $size; $i++ ) {
			$mask = 0x80;
			for ( $j = 0; $j < 8; $j++ ) {
				if ( $data[ $i ] & $mask ) {
					$b_stream->data[ $bit_pos ] = 1;
				} else {
					$b_stream->data[ $bit_pos ] = 0;
				}
				$bit_pos++;
				$mask = $mask >> 1;
			}
		}

		return $b_stream;
	}

	/**
	 * Append another bitstream to the current one.
	 *
	 * @param QrcdrBitstream $bitstream_to_append The stream to append.
	 * @return int 0 on success, -1 on error.
	 */
	public function append( QrcdrBitstream $bitstream_to_append ) {
		if ( null === $bitstream_to_append ) {
			return -1;
		}

		if ( 0 === $bitstream_to_append->size() ) {
			return 0;
		}

		if ( 0 === $this->size() ) {
			$this->data = $bitstream_to_append->data;
			return 0;
		}

		$this->data = array_values( array_merge( $this->data, $bitstream_to_append->data ) );

		return 0;
	}

	/**
	 * Convert a number to bits and append it.
	 *
	 * @param int $bits Number of bits.
	 * @param int $num  The number.
	 * @return int 0 on success, -1 on error.
	 */
	public function appendNum( $bits, $num ) {
		if ( 0 === $bits ) {
			return 0;
		}

		$new_bstream = QrcdrBitstream::newFromNum( $bits, $num );

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

		$return_code = $this->append( $new_bstream );
		unset( $new_bstream );

		return $return_code;
	}

	/**
	 * Convert a byte array to bits and append it.
	 *
	 * @param int   $size Size of bytes.
	 * @param array $data Byte array.
	 * @return int 0 on success, -1 on error.
	 */
	public function appendBytes( $size, $data ) {
		if ( 0 === $size ) {
			return 0;
		}

		$new_bstream = QrcdrBitstream::newFromBytes( $size, $data );

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

		$return_code = $this->append( $new_bstream );
		unset( $new_bstream );

		return $return_code;
	}

	/**
	 * Convert the bitstream back to a byte array.
	 *
	 * @return array The resulting byte array.
	 */
	public function toByte() {
		$bit_count = $this->size();

		if ( 0 === $bit_count ) {
			return array();
		}

		$byte_array = array_fill( 0, (int) ( ( $bit_count + 7 ) / 8 ), 0 );
		$full_bytes = (int) ( $bit_count / 8 );
		$bit_pos    = 0;

		// Process full bytes
		for ( $i = 0; $i < $full_bytes; $i++ ) {
			$byte_value = 0;
			for ( $j = 0; $j < 8; $j++ ) {
				$byte_value = $byte_value << 1;
				$byte_value |= $this->data[ $bit_pos ];
				$bit_pos++;
			}
			$byte_array[ $i ] = $byte_value;
		}

		// Process remaining bits
		if ( $bit_count & 7 ) {
			$byte_value = 0;
			for ( $j = 0; $j < ( $bit_count & 7 ); $j++ ) {
				$byte_value = $byte_value << 1;
				$byte_value |= $this->data[ $bit_pos ];
				$bit_pos++;
			}
			$byte_array[ $full_bytes ] = $byte_value;
		}

		return $byte_array;
	}
}
