<?php

function getEncoding($encoding)
{
	if (null === $encoding) {
		return 'UTF-8';
	}

	if ('UTF-8' === $encoding) {
		return 'UTF-8';
	}

	$encoding = strtoupper($encoding);

	if ('8BIT' === $encoding || 'BINARY' === $encoding) {
		return 'CP850';
	}

	if ('UTF8' === $encoding) {
		return 'UTF-8';
	}

	return $encoding;
}

if (!function_exists('mb_chr')) {
	function mb_chr($code, $encoding = null)
	{
		if (0x80 > $code %= 0x200000) {
			$s = \chr($code);
		} elseif (0x800 > $code) {
			$s = \chr(0xC0 | $code >> 6) . \chr(0x80 | $code & 0x3F);
		} elseif (0x10000 > $code) {
			$s = \chr(0xE0 | $code >> 12) . \chr(0x80 | $code >> 6 & 0x3F) . \chr(0x80 | $code & 0x3F);
		} else {
			$s = \chr(0xF0 | $code >> 18) . \chr(0x80 | $code >> 12 & 0x3F) . \chr(0x80 | $code >> 6 & 0x3F) . \chr(0x80 | $code & 0x3F);
		}

		if ('UTF-8' !== $encoding = getEncoding($encoding)) {
			$s = mb_convert_encoding($s, $encoding, 'UTF-8');
		}

		return $s;
	}
}

if (!function_exists('mb_strlen')) {
	function mb_strlen($s, $encoding = null)
	{
		$encoding = getEncoding($encoding);
		if ('CP850' === $encoding || 'ASCII' === $encoding) {
			return \strlen($s);
		}

		return @iconv_strlen($s, $encoding);
	}
}

if (!function_exists('mb_substr')) {
	function mb_substr($s, $start, $length = null, $encoding = null)
	{
		$encoding = getEncoding($encoding);
		if ('CP850' === $encoding || 'ASCII' === $encoding) {
			return (string) substr($s, $start, null === $length ? 2147483647 : $length);
		}

		if ($start < 0) {
			$start = iconv_strlen($s, $encoding) + $start;
			if ($start < 0) {
				$start = 0;
			}
		}

		if (null === $length) {
			$length = 2147483647;
		} elseif ($length < 0) {
			$length = iconv_strlen($s, $encoding) + $length - $start;
			if ($length < 0) {
				return '';
			}
		}

		return (string) iconv_substr($s, $start, $length, $encoding);
	}
}

if (!function_exists('mb_ereg')) {
	function mb_ereg($pattern, $string, &$matches = null, $options = 'ms')
	{
		$pattern = '/' . str_replace('/', '\/', $pattern) . '/' . $options;
		$result = preg_match($pattern, $string, $matches);
		return $result === 1;
	}
}

if (!function_exists('mb_ord')) {
	function mb_ord($s, $encoding = null)
	{
		if ('UTF-8' !== $encoding = getEncoding($encoding)) {
			$s = mb_convert_encoding($s, 'UTF-8', $encoding);
		}

		if (1 === \strlen($s)) {
			return \ord($s);
		}

		$code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
		if (0xF0 <= $code) {
			return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
		}
		if (0xE0 <= $code) {
			return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
		}
		if (0xC0 <= $code) {
			return (($code - 0xC0) << 6) + $s[2] - 0x80;
		}

		return $code;
	}
}


if (!function_exists('mb_encode_numericentity')) {
	function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
	{
		if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
			trigger_error('mb_encode_numericentity() expects parameter 1 to be string, ' . \gettype($s) . ' given', \E_USER_WARNING);

			return null;
		}

		if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) {
			return false;
		}

		if (null !== $encoding && !\is_scalar($encoding)) {
			trigger_error('mb_encode_numericentity() expects parameter 3 to be string, ' . \gettype($s) . ' given', \E_USER_WARNING);

			return null;  // Instead of '' (cf. mb_decode_numericentity).
		}

		if (null !== $is_hex && !\is_scalar($is_hex)) {
			trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, ' . \gettype($s) . ' given', \E_USER_WARNING);

			return null;
		}

		$s = (string) $s;
		if ('' === $s) {
			return '';
		}

		$encoding = getEncoding($encoding);

		if ('UTF-8' === $encoding) {
			$encoding = null;
			if (!preg_match('//u', $s)) {
				$s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
			}
		} else {
			$s = iconv($encoding, 'UTF-8//IGNORE', $s);
		}

		static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];

		$cnt = floor(\count($convmap) / 4) * 4;
		$i = 0;
		$len = \strlen($s);
		$result = '';

		while ($i < $len) {
			$ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
			$uchr = substr($s, $i, $ulen);
			$i += $ulen;
			$c = mb_ord($uchr);

			for ($j = 0; $j < $cnt; $j += 4) {
				if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
					$cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
					$result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#' . $cOffset . ';';
					continue 2;
				}
			}
			$result .= $uchr;
		}

		if (null === $encoding) {
			return $result;
		}

		return iconv('UTF-8', $encoding . '//IGNORE', $result);
	}
}

/**
 * Implementation of function json_decode on PHP
 *
 * @author Alexander Muzychenko
 * @link https://github.com/alexmuz/php-json
 * @see http://php.net/json_decode
 * @license GNU Lesser General Public License (LGPL) http://www.gnu.org/copyleft/lesser.html
 */
if (!function_exists('json_decode')) {

	function json_decode($json, $assoc = false)
	{
		$i = 0;
		$n = strlen($json);
		try {
			$result = json_decode_value($json, $i, $assoc);
			while ($i < $n && $json[$i] && $json[$i] <= ' ') $i++;
			if ($i < $n) {
				return null;
			}
			return $result;
		} catch (Exception $e) {
			return null;
		}
	}

	function json_decode_value($json, &$i, $assoc = false)
	{
		$n = strlen($json);
		while ($i < $n && $json[$i] && $json[$i] <= ' ') $i++;

		switch ($json[$i]) {
			// object
			case '{':
				$i++;
				$result = $assoc ? array() : new stdClass();
				while ($i < $n && $json[$i] && $json[$i] <= ' ') $i++;
				if ($json[$i] === '}') {
					$i++;
					return $result;
				}
				while ($i < $n) {
					$key = json_decode_string($json, $i);
					while ($i < $n && $json[$i] && $json[$i] <= ' ') $i++;
					if ($json[$i++] != ':') {
						throw new Exception("Expected ':' on " . ($i - 1));
					}
					if ($assoc) {
						$result[$key] = json_decode_value($json, $i, $assoc);
					} else {
						$result->$key = json_decode_value($json, $i, $assoc);
					}
					while ($i < $n && $json[$i] && $json[$i] <= ' ') $i++;
					if ($json[$i] === '}') {
						$i++;
						return $result;
					}
					if ($json[$i++] != ',') {
						throw new Exception("Expected ',' on " . ($i - 1));
					}
					while ($i < $n && $json[$i] && $json[$i] <= ' ') $i++;
				}
				throw new Exception("Syntax error");
				// array
			case '[':
				$i++;
				$result = array();
				while ($i < $n && $json[$i] && $json[$i] <= ' ') $i++;
				if ($json[$i] === ']') {
					$i++;
					return array();
				}
				while ($i < $n) {
					$result[] = json_decode_value($json, $i, $assoc);
					while ($i < $n && $json[$i] && $json[$i] <= ' ') $i++;
					if ($json[$i] === ']') {
						$i++;
						return $result;
					}
					if ($json[$i++] != ',') {
						throw new Exception("Expected ',' on " . ($i - 1));
					}
					while ($i < $n && $json[$i] && $json[$i] <= ' ') $i++;
				}
				throw new Exception("Syntax error");
				// string
			case '"':
				return json_decode_string($json, $i);
				// number
			case '-':
				return json_decode_number($json, $i);
				// true
			case 't':
				if ($i + 3 < $n && substr($json, $i, 4) === 'true') {
					$i += 4;
					return true;
				}
				// false
			case 'f':
				if ($i + 4 < $n && substr($json, $i, 5) === 'false') {
					$i += 5;
					return false;
				}
				// null
			case 'n':
				if ($i + 3 < $n && substr($json, $i, 4) === 'null') {
					$i += 4;
					return null;
				}
			default:
				// number
				if ($json[$i] >= '0' && $json[$i] <= '9') {
					return json_decode_number($json, $i);
				} else {
					throw new Exception("Syntax error");
				};
		}
	}

	function json_decode_string($json, &$i)
	{
		$result = '';
		$escape = array('"' => '"', '\\' => '\\', '/' => '/', 'b' => "\b", 'f' => "\f", 'n' => "\n", 'r' => "\r", 't' => "\t");
		$n = strlen($json);
		if ($json[$i] === '"') {
			while (++$i < $n) {
				if ($json[$i] === '"') {
					$i++;
					return $result;
				} elseif ($json[$i] === '\\') {
					$i++;
					if ($json[$i] === 'u') {
						$code = hexdec(substr($json, $i + 1, 4));
						// Convert Unicode code point to UTF-8 without mbstring
						if ($code < 0x80) {
							$result .= chr($code);
						} elseif ($code < 0x800) {
							$result .= chr(0xC0 | ($code >> 6)) . chr(0x80 | ($code & 0x3F));
						} else {
							$result .= chr(0xE0 | ($code >> 12)) . chr(0x80 | (($code >> 6) & 0x3F)) . chr(0x80 | ($code & 0x3F));
						}
						$i += 4;
					} elseif (isset($escape[$json[$i]])) {
						$result .= $escape[$json[$i]];
					} else {
						break;
					}
				} else {
					$result .= $json[$i];
				}
			}
		}
		throw new Exception("Syntax error");
	}

	function json_decode_number($json, &$i)
	{
		$result = '';
		if ($json[$i] === '-') {
			$result = '-';
			$i++;
		}
		$n = strlen($json);
		while ($i < $n && $json[$i] >= '0' && $json[$i] <= '9') {
			$result .= $json[$i++];
		}

		if ($i < $n && $json[$i] === '.') {
			$result .= '.';
			$i++;
			while ($i < $n && $json[$i] >= '0' && $json[$i] <= '9') {
				$result .= $json[$i++];
			}
		}
		if ($i < $n && ($json[$i] === 'e' || $json[$i] === 'E')) {
			$result .= $json[$i];
			$i++;
			if ($json[$i] === '-' || $json[$i] === '+') {
				$result .= $json[$i++];
			}
			while ($i < $n && $json[$i] >= '0' && $json[$i] <= '9') {
				$result .= $json[$i++];
			}
		}

		return (0 + $result);
	}
}

/**
 * Implementation of function json_encode on PHP
 * 
 * @author Alexander Muzychenko
 * @link https://github.com/alexmuz/php-json
 * @see http://php.net/json_encode
 * @license GNU Lesser General Public License (LGPL) http://www.gnu.org/copyleft/lesser.html
 */
if (!function_exists('json_encode')) {
	function json_encode($value)
	{
		if (is_int($value)) {
			return (string)$value;
		} elseif (is_string($value)) {
			$value = str_replace(
				array('\\', '/', '"', "\r", "\n", "\b", "\f", "\t"),
				array('\\\\', '\/', '\"', '\r', '\n', '\b', '\f', '\t'),
				$value
			);
			$convmap = array(0x80, 0xFFFF, 0, 0xFFFF);
			$result = "";
			for ($i = mb_strlen($value) - 1; $i >= 0; $i--) {
				$mb_char = mb_substr($value, $i, 1);
				if (mb_ereg("&#(\\d+);", mb_encode_numericentity($mb_char, $convmap, "UTF-8"), $match)) {
					$result = sprintf("\\u%04x", $match[1]) . $result;
				} else {
					$result = $mb_char . $result;
				}
			}
			return '"' . $result . '"';
		} elseif (is_float($value)) {
			return str_replace(",", ".", $value);
		} elseif (is_null($value)) {
			return 'null';
		} elseif (is_bool($value)) {
			return $value ? 'true' : 'false';
		} elseif (is_array($value)) {
			$with_keys = false;
			$n = count($value);
			for ($i = 0, reset($value); $i < $n; $i++, next($value)) {
				if (key($value) !== $i) {
					$with_keys = true;
					break;
				}
			}
		} elseif (is_object($value)) {
			$with_keys = true;
		} else {
			return '';
		}
		$result = array();
		if ($with_keys) {
			foreach ($value as $key => $v) {
				$result[] = json_encode((string)$key) . ':' . json_encode($v);
			}
			return '{' . implode(',', $result) . '}';
		} else {
			foreach ($value as $key => $v) {
				$result[] = json_encode($v);
			}
			return '[' . implode(',', $result) . ']';
		}
	}
}

/**
 * Translates Latin characters to Cyrillic characters in a given subject.
 *
 * @param string $subject The subject to be translated.
 * @param bool $whitespace Flag to indicate whether to replace whitespace characters or not. Default is false.
 * @return string The translated subject with Cyrillic characters.
 */
function getCyrillicAlphabet($subject, $whitespace = false)
{
	$search =	["A", "a", "B", "E", "e", "M", "H", "O", "o", "P", "p", "C", "c", "T", "X", "x", "I", "i"];
	$replace =	["А", "а", "В", "Е", "е", "М", "Н", "О", "о", "Р", "р", "С", "с", "Т", "Х", "х", "І", "і"];

	if ($whitespace) {
		$search[] = " ";
		$replace[] = "‏‏‎‎  ";
	}

	return nl2br(str_replace($search, $replace, $subject));
}

/**
 * Retrieves the card information based on the given BIN.
 * @param string $bin The BIN to be used for the lookup.
 * @param string|null $proxy The proxy to be used for the request.
 * @param string|null $proxyUserPwd The proxy user and password to be used for the request.
 * @return array The card information.
 */
function getCardInfo($bin, $proxy = null, $proxyUserPwd = null, $caCertPath = null)
{
	$ch = curl_init();

	$url = "https://lookup.binlist.net/$bin";

	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');

	// Disable SSL verification (optional; use cautiously in production)
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

	// Set the required header
	$headers = array();
	$headers[] = 'Accept-Version: 3';
	curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

	// If a proxy is provided, configure cURL to use it
	if ($proxy) {
		curl_setopt($ch, CURLOPT_PROXY, $proxy);

		// If the proxy requires authentication
		if ($proxyUserPwd) {
			curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyUserPwd);
		}
	}

	// If a CA certificate is provided, set the CAINFO option
	if ($caCertPath) {
		curl_setopt($ch, CURLOPT_CAINFO, $caCertPath);
		// Ensure SSL verification is enabled when using a CA certificate
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
	}

	// Execute the request
	$output = curl_exec($ch);

	if (curl_errno($ch)) {
		echo 'Error: ' . curl_error($ch);
	}

	curl_close($ch);

	return json_decode($output, true);
}

/**
 * Converts a Unicode codepoint to UTF-8 string (pure PHP replacement for mb_chr)
 * @param int $code Unicode codepoint
 * @return string UTF-8 encoded character
 */
function unicode_chr(int $code): string
{
	if ($code < 0x80) {
		return chr($code);
	} elseif ($code < 0x800) {
		return chr(0xC0 | ($code >> 6)) . chr(0x80 | ($code & 0x3F));
	} elseif ($code < 0x10000) {
		return chr(0xE0 | ($code >> 12)) . chr(0x80 | (($code >> 6) & 0x3F)) . chr(0x80 | ($code & 0x3F));
	} else {
		return chr(0xF0 | ($code >> 18)) . chr(0x80 | (($code >> 12) & 0x3F)) . chr(0x80 | (($code >> 6) & 0x3F)) . chr(0x80 | ($code & 0x3F));
	}
}

/**
 * Retrieves the country name based on the given country code.
 * @param string $countryCode The country code to be used for the lookup.
 * @return string The country name.
 */
function country2flag(string $countryCode): string
{
	return (string) preg_replace_callback(
		'/./',
		static function (array $letter) {
			return unicode_chr(ord($letter[0]) % 32 + 0x1F1E5);
		},
		$countryCode
	);
}
