<?php

error_reporting(-1);
mb_internal_encoding('utf-8');

abstract class Gender
{
    const FEMININE = 0;
    const MASCULINE = 1;
}

class Lexeme
{
    public function __construct(string $form1, string $form2, string $form5, int $gender = Gender::MASCULINE)
    {
        $this->form1 = $form1;
        $this->form2 = $form2;
        $this->form5 = $form5;
        $this->gender = $gender;
    }

    public function getGender(): int
    {
        return $this->gender;
    }

    public function getFormOf(int $number): string
    {
        $digit0 = $number % 10;
        $digit1 = $number / 10 % 10;

        if ($digit1 == 1) {
            return $this->form5;
        } elseif ($digit0 == 1) {
            return $this->form1;
        } elseif ($digit0 > 1 && $digit0 <= 4) {
            return $this->form2;
        } else {
            return $this->form5;
        }
    }

    public static function getEmpty(): Lexeme
    {
        return new Lexeme("", "", "");
    }

    private int $gender;
    private string $form1;
    private string $form2;
    private string $form5;
}

class InWordsBuilder
{
    public function __construct(Lexeme $quantity)
    {
        self::$thousand = new Lexeme("тысяча", "тысячи", "тысяч", Gender::FEMININE);
        self::$million = new Lexeme("миллион", "миллиона", "миллионов", Gender::MASCULINE);
        self::$billion = new Lexeme("миллиард", "миллиарда", "миллиардов", Gender::MASCULINE);
        $this->quantity = $quantity;
    }

    public function build(int $number): string
    {
        if ($number == 0) {
            $quantityStr = $this->quantity->getFormOf(0);
            return "ноль" . ($quantityStr == "" ? "" : " $quantityStr");
        }
        $inParts = new IntegerInParts($number);
        $partsOfRecord = [];
        if ($number >= 1000000000) {
            $partsOfRecord[0] = self::writePartInWords($inParts->getBillions(), self::$billion);
        }
        if ($number >= 1000000) {
            $partsOfRecord[1] = self::writePartInWords($inParts->getMillions(), self::$million);
        }
        if ($number >= 1000) {
            $partsOfRecord[2] = self::writePartInWords($inParts->getThousands(), self::$thousand);
        }
        $partsOfRecord[3] = self::writePartInWords($inParts->getUnits(), $this->quantity);
        return trim(implode(" ", $partsOfRecord));
    }

    private static function writePartInWords(int $number, Lexeme $lexeme): string
    {
        $digits = [
            $number / 100 % 10,
            $number / 10 % 10,
            $number % 10
        ];
        $numberMod100 = $number % 100;

        $parts = [self::$hundreds[$digits[0]]];
        if ($digits[1] <= 1) {
            if ($digits[2] < 3) {
                $parts[1] = self::$oneToThree[$lexeme->getGender()][$digits[2]];
            } else {
                $parts[1] = self::$threeToNineteen[$numberMod100];
            }
        } else {
            $parts[1] = self::$tens[$digits[1]];
            if ($digits[2] < 3) {
                $parts[2] = self::$oneToThree[$lexeme->getGender()][$digits[2]];
            } else {
                $parts[2] = self::$threeToNineteen[$digits[2]];
            }
        }
        $numeral = trim(implode(" ", $parts));
        $lexemeInForm = $lexeme->getFormOf($number);
        return $numeral . ($lexemeInForm == "" ? "" : " $lexemeInForm");
    }

    private static array $hundreds = ["", "сто", "двести", "триста", "четыреста", "пятьсот",
        "шестьсот", "семьсот", "восемьсот", "девятьсот"];
    private static array $tens = [2 => "двадцать", "тридцать", "сорок", "пятьдесят", "шестьдесят", "семьдесят",
        "восемьдесят", "девяносто"];
    private static array $threeToNineteen = [3 => 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять', 'десять',
        'одиннадцать', 'двенадцать', 'тринадцать', 'четырнадцать', 'пятнадцать', 'шестнадцать', 'семнадцать',
        'восемнадцать', 'девятнадцать'];
    private static array $oneToThree = [
        Gender::FEMININE => ['', 'одна', 'две'],
        Gender::MASCULINE => ['', 'один', 'два'],
    ];
    private static Lexeme $thousand;
    private static Lexeme $million;
    private static Lexeme $billion;
    private Lexeme $quantity;
}

class IntegerInParts
{
    public function __construct(int $number)
    {
        $this->billions = $number / 1000000000 % 1000;
        $this->millions = $number / 1000000 % 1000;
        $this->thousands = $number / 1000 % 1000;
        $this->units = $number % 1000;
    }

    public function getBillions(): int
    {
        return $this->billions;
    }

    public function getMillions(): int
    {
        return $this->millions;
    }

    public function getThousands(): int
    {
        return $this->thousands;
    }

    public function getUnits(): int
    {
        return $this->units;
    }

    private int $billions;
    private int $millions;
    private int $thousands;
    private int $units;
}

$builder = new InWordsBuilder(new Lexeme("штука", "штуки", "штук", Gender::FEMININE));
$numbers = [0, 4, 5, 9,
    49, 55, 66, 28, 97, 18, 85, 96,
    673, 242, 463, 592, 664, 259, 170, 382,
    6428, 5454, 2175, 6234, 7091, 5236, 4533, 1244,
    99189, 70540, 64063, 78756, 35080, 15626, 36327, 84276,
    528070, 760165, 873158, 566844, 843370, 415865, 997742, 890570,
    6855775, 4913491, 6181936, 7298680, 7625891, 7733251, 1001001, 9147526,
    75269465, 98207441, 46370258, 26467052, 67385218, 74961612, 97937978, 63423923,
    313248348, 604730178, 342676693, 965300262, 175878814, 796606802, 233396657, 213578532,
    5281552249, 2361786413, 9463336049, 6982021856, 8707095076, 7869601075, 4398378123, 2223366077];
foreach ($numbers as $number) {
    printf("%-12d= %s\n", $number, $builder->build($number));
}
