<?php

/*
 *
 *  ____            _        _   __  __ _                  __  __ ____
 * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
 * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
 * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * @author PocketMine Team
 * @link http://www.pocketmine.net/
 *
 *
 */

declare(strict_types=1);

namespace pocketmine\build\generate_runtime_enum_serializers;

use pocketmine\block\utils\BellAttachmentType;
use pocketmine\block\utils\CopperOxidation;
use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\LeverFacing;
use pocketmine\block\utils\MushroomBlockType;
use pocketmine\block\utils\SkullType;
use pocketmine\block\utils\SlabType;
use pocketmine\item\PotionType;
use function array_key_first;
use function array_keys;
use function array_map;
use function ceil;
use function count;
use function dirname;
use function file_put_contents;
use function implode;
use function ksort;
use function log;
use function ob_get_clean;
use function ob_start;

require dirname(__DIR__) . '/vendor/autoload.php';

/**
 * @param string[] $memberNames
 * @phpstan-param list<string> $memberNames
 *
 * @return string[]
 * @phpstan-return list<string>
 */
function buildWriterFunc(string $virtualTypeName, string $nativeTypeName, array $memberNames, string &$functionName) : array{
	$bits = getBitsRequired($memberNames);
	$lines = [];

	$functionName = "write$virtualTypeName";
	$lines[] = "public static function $functionName(RuntimeDataWriter \$w, \\$nativeTypeName \$value) : void{";
	$lines[] = "\t\$w->writeInt($bits, match(\$value){";

	foreach($memberNames as $key => $memberName){
		$lines[] = "\t\t$memberName => $key,";
	}
	$lines[] = "\t\tdefault => throw new \pocketmine\utils\AssumptionFailedError(\"All $virtualTypeName cases should be covered\")";
	$lines[] = "\t});";
	$lines[] = "}";

	return $lines;
}

/**
 * @param string[] $memberNames
 * @phpstan-param list<string> $memberNames
 *
 * @return string[]
 * @phpstan-return list<string>
 */
function buildReaderFunc(string $virtualTypeName, string $nativeTypeName, array $memberNames, string &$functionName) : array{
	$bits = getBitsRequired($memberNames);
	$lines = [];

	$functionName = "read$virtualTypeName";
	$lines[] = "public static function $functionName(RuntimeDataReader \$r) : \\$nativeTypeName{";
	$lines[] = "\treturn match(\$r->readInt($bits)){";

	foreach($memberNames as $key => $memberName){
		$lines[] = "\t\t$key => $memberName,";
	}
	$lines[] = "\t\tdefault => throw new InvalidSerializedRuntimeDataException(\"Invalid serialized value for $virtualTypeName\")";
	$lines[] = "\t};";
	$lines[] = "}";

	return $lines;
}

/**
 * @param mixed[] $members
 */
function getBitsRequired(array $members) : int{
	return (int) ceil(log(count($members), 2));
}

/**
 * @param object[] $members
 * @phpstan-param array<string, object> $members
 *
 * @return string[]
 * @phpstan-return list<string>
 */
function stringifyEnumMembers(array $members, string $enumClass) : array{
	ksort($members, SORT_STRING);
	return array_map(fn(string $enumCaseName) => "\\$enumClass::$enumCaseName()", array_keys($members));
}

/**
 * @param object[] $enumMembers
 * @phpstan-param array<string, object> $enumMembers
 *
 * @return string[]
 * @phpstan-return list<string>
 */
function buildEnumWriterFunc(array $enumMembers, string &$functionName) : array{
	$reflect = new \ReflectionClass($enumMembers[array_key_first($enumMembers)]);
	return buildWriterFunc(
		$reflect->getShortName(),
		$reflect->getName(),
		stringifyEnumMembers($enumMembers, $reflect->getName()),
		$functionName
	);
}

/**
 * @param object[] $enumMembers
 * @phpstan-param array<string, object> $enumMembers
 *
 * @return string[]
 * @phpstan-return list<string>
 */
function buildEnumReaderFunc(array $enumMembers, string &$functionName) : array{
	if(count($enumMembers) === 0){
		throw new \InvalidArgumentException("Enum members cannot be empty");
	}
	$reflect = new \ReflectionClass($enumMembers[array_key_first($enumMembers)]);
	return buildReaderFunc(
		$reflect->getShortName(),
		$reflect->getName(),
		stringifyEnumMembers($enumMembers, $reflect->getName()),
		$functionName
	);
}

$enumsUsed = [
	BellAttachmentType::getAll(),
	CopperOxidation::getAll(),
	CoralType::getAll(),
	DyeColor::getAll(),
	LeverFacing::getAll(),
	MushroomBlockType::getAll(),
	SkullType::getAll(),
	SlabType::getAll(),
	PotionType::getAll()
];

$readerFuncs = [];
$writerFuncs = [];
$functionName = "";

foreach($enumsUsed as $enumMembers){
	$writerF = buildEnumWriterFunc($enumMembers, $functionName);
	/** @var string $functionName */
	$writerFuncs[$functionName] = $writerF;
	$readerF = buildEnumReaderFunc($enumMembers, $functionName);
	/** @var string $functionName */
	$readerFuncs[$functionName] = $readerF;
}

/**
 * @param string[][] $functions
 * @phpstan-param array<string, list<string>> $functions
 */
function printFunctions(array $functions, string $className) : void{
	ksort($functions, SORT_STRING);

	ob_start();

	echo <<<'HEADER'
<?php

/*
 *
 *  ____            _        _   __  __ _                  __  __ ____
 * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
 * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
 * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * @author PocketMine Team
 * @link http://www.pocketmine.net/
 *
 *
 */

declare(strict_types=1);

namespace pocketmine\data\runtime;

/**
 * This class is auto-generated. Do not edit it manually.
 * @see build/generate-runtime-enum-serializers.php
 */

HEADER;

	echo "final class $className{\n\n";
	echo implode("\n\n", array_map(fn(array $functionLines) => "\t" . implode("\n\t", $functionLines), $functions));
	echo "\n\n}\n";

	file_put_contents(dirname(__DIR__) . '/src/data/runtime/' . $className . '.php', ob_get_clean());
}

printFunctions($writerFuncs, "RuntimeEnumSerializer");
printFunctions($readerFuncs, "RuntimeEnumDeserializer");

echo "Done. Don't forget to run CS fixup after generating code.\n";