<?php

declare(strict_types=1);

namespace core\handler;

use core\referencecache\ReferencesCache;
use Exception;
use utils\CommandInterface;
use utils\exception\HypertubeArgumentsMismatchException;
use utils\RuntimeName;
use utils\type\CommandType;

abstract class AbstractHandler
{
    private static array $HANDLER_MAP = [];
    private static bool $isInitializing = false;

    /**
     * @return mixed
     */
    abstract public function process(CommandInterface $command);

    public function __construct()
    {
        if (!self::$isInitializing && empty(self::$HANDLER_MAP)) {
            self::$isInitializing = true;
            try {
                self::$HANDLER_MAP = Handler::getHandlers();
            } finally {
                self::$isInitializing = false;
            }
        }
    }

    public function handleCommand(CommandInterface $command)
    {
        if ($command->getCommandType()->equalsByValue(CommandType::GENERATE_LIB)) {
            return $this->process($command);
        }

        $this->iterate($command);

        return $this->process($command);
    }


    /**
     * @throws Exception
     */
    public function iterate(CommandInterface $command): void
    {
        foreach ($command->getPayload() as $index => $payloadItem) {
            if ($payloadItem instanceof CommandInterface) {
                $innerCommand = $payloadItem;
                if (
                    $innerCommand->getCommandType()->equalsByValue(CommandType::REFERENCE)
                ) {
                    $referenceGuid = (string) ($innerCommand->getPayload()[0] ?? '');
                    $resolvedReference = &ReferencesCache::getInstance()->resolveReferenceByRef($referenceGuid);
                    $command->setPayloadByRef($index, $resolvedReference);
                    continue;
                }

                $command->setPayload($index, self::$HANDLER_MAP[$innerCommand->getCommandType()->getValue()]->handleCommand($innerCommand));
            }
        }
    }

    protected function validate(CommandInterface $command, int $requiredParametersCount, string $callerTypeName): void
    {
        if ($command->getPayloadSize() < $requiredParametersCount) {
            throw new HypertubeArgumentsMismatchException($callerTypeName, $requiredParametersCount);
        }
    }

}
