<?php

declare(strict_types=1);

namespace PhpCfdi\Credentials\Internal;

use UnexpectedValueException;

/**
 * Converts any string of any base to any other base without
 * PHP native method base_convert's double and float limitations.
 *
 * @see https://php.net/base_convert
 * Original author: https://github.com/credomane/php_baseconvert
 *
 * @internal
 */
class BaseConverter
{
    public function __construct(private readonly BaseConverterSequence $sequence)
    {
    }

    public static function createBase36(): self
    {
        return new self(new BaseConverterSequence('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'));
    }

    public function sequence(): BaseConverterSequence
    {
        return $this->sequence;
    }

    public function maximumBase(): int
    {
        return $this->sequence->length();
    }

    public function convert(string $input, int $frombase, int $tobase): string
    {
        if ($frombase < 2 || $frombase > $this->maximumBase()) {
            throw new UnexpectedValueException('Invalid from base');
        }
        if ($tobase < 2 || $tobase > $this->maximumBase()) {
            throw new UnexpectedValueException('Invalid to base');
        }

        $originalSequence = $this->sequence()->value();
        if ('' === $input) {
            $input = $originalSequence[0]; // use zero as input
        }
        $chars = substr($originalSequence, 0, $frombase);
        if (! preg_match("/^[$chars]+$/", $input)) {
            throw new UnexpectedValueException('The number to convert contains invalid characters');
        }

        $length = strlen($input);
        $values = [];
        for ($i = 0; $i < $length; $i++) {
            $values[] = intval(stripos($originalSequence, $input[$i]));
        }

        $result = '';
        do {
            $divide = 0;
            $newlen = 0;
            for ($i = 0; $i < $length; $i++) {
                $divide = $divide * $frombase + $values[$i];
                if ($divide >= $tobase) {
                    $values[$newlen] = intval($divide / $tobase);
                    $divide = $divide % $tobase;
                    $newlen = $newlen + 1;
                } elseif ($newlen > 0) {
                    $values[$newlen] = 0;
                    $newlen = $newlen + 1;
                }
            }
            $length = $newlen;
            $result = $originalSequence[$divide] . $result;
        } while ($newlen > 0);

        return $result;
    }
}
