<?php

namespace App\Classes;

use App\Interfaces\CRC as CRCInterface;

class Crypt implements CRCInterface
{
	private array $keys= [
		305419896,
		591751049,
		878082192,
	];

	public function __construct(string $password)
	{
		$this->extend_key($password);
	}

	/**
	 * Update crc.
	 */
	private function crc32_cr(int $oldCrc, int $charAt): int
	{
		return (($oldCrc >> 8) & 0xFFFFFF) ^ self::crc32_tab[($oldCrc ^ $charAt) & 0xFF];
	}

	public function decrypt(string $content): string
	{
		$result = '';

		foreach (unpack('C*', $content) as $byte) {
			$byte = ($byte ^ $this->decrypt_byte()) & 0xFF;
			$this->update_keys($byte);
			$result .= chr($byte);
		}

		return $result;
	}

	/**
	 * Decrypt byte.
	 */
	private function decrypt_byte(): int
	{
		$temp = $this->keys[2] | 2;

		return (($temp * ($temp ^ 1)) >> 8) & 0xFFFFFF;
	}

	public function encrypt(string $content): string
	{
		$result = '';

		foreach (unpack('C*', $content) as $val)
			$result .= pack('c', $this->encrypt_byte($val));

		return $result;
	}

	private function encrypt_byte(int $byte): int
	{
		$result = $byte ^ $this->decrypt_byte() & 0xFF;
		$this->update_keys($byte);

		return $result;
	}

	public function extend_key(string $password): void
	{
		foreach (unpack('C*', $password) as $byte)
			$this->update_keys($byte);
	}

	public static function toSignedInt32(int $int): int
	{
		if (\PHP_INT_SIZE === 8) {
			$int &= 0xFFFFFFFF;

			if ($int & 0x80000000)
				return $int - 0x100000000;
		}

		return $int;
	}

	/**
	 * Update keys.
	 */
	private function update_keys(int $byte): void
	{
		$this->keys[0] = $this->crc32_cr($this->keys[0],$byte);
		$this->keys[1] += ($this->keys[0] & 0xFF);
		$this->keys[1] = $this->toSignedInt32($this->keys[1]*134775813+1);
		$this->keys[2] = $this->toSignedInt32($this->crc32_cr($this->keys[2],($this->keys[1] >> 24) & 0xFF));
	}
}