<file_sep><?php
declare(strict_types=1);


namespace Solaing\FlowerBouquets\Exceptions;


final class InvalidBouquetSize extends \InvalidArgumentException
{
    public static function fromCharacter(string $size): self
    {
        return new self("The bouquet size '$size'' is invalid");
    }
}<file_sep><?php
declare(strict_types=1);


namespace Solaing\FlowerBouquets\Tests\Factories;


use Faker\Factory;
use Solaing\FlowerBouquets\Aggregates\Bouquet;
use Solaing\FlowerBouquets\Entities\BouquetName;
use Solaing\FlowerBouquets\Entities\Flower;
use Solaing\FlowerBouquets\Entities\BouquetSize;

final class BouquetFactory
{
    public static function build(string $name, string $size, array $flowers, int $totalOfFlowers): Bouquet
    {
        return new Bouquet(
            new BouquetName(strtoupper($name)),
            new BouquetSize($size),
            $flowers,
            $totalOfFlowers
        );
    }

    /**
     * @param Flower[] $flowers
     * @param int $totalOfFlowers
     * @return Bouquet
     */
    public static function buildWithFlowers(array $flowers, int $totalOfFlowers): Bouquet
    {
        $faker = Factory::create();

        return new Bouquet(
            new BouquetName(strtoupper($faker->randomLetter)),
            new BouquetSize($faker->randomElement(['S', 'L'])),
            $flowers,
            $totalOfFlowers
        );
    }

    public static function buildWithNameAndSize(string $name, string $size): Bouquet
    {
        return new Bouquet(
            new BouquetName($name),
            new BouquetSize($size),
            [],
            0
        );
    }
}<file_sep><?php
declare(strict_types=1);

namespace Solaing\FlowerBouquets;

use Solaing\FlowerBouquets\Input\GenerateFlowerBouquetContainer;
use Solaing\FlowerBouquets\Output\GenerateBouquetCollection;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class GenerateBouquets
{
    private $output;
    /**
     * @var StreamableInputInterface
     */
    private $input;

    public function __construct(StreamableInputInterface $input, OutputInterface $output) {
        $this->output = $output;
        $this->input = $input;
    }

    public function exec(): void
    {
        $container = GenerateFlowerBouquetContainer::fromResource($this->input->getStream());

        $bouquets = GenerateBouquetCollection::fromContainer($container);

        $this->output->writeln("-------------------------");
        $this->output->writeln("----- Flower Bouquets ---");
        $this->output->writeln("-------------------------");
        foreach ($bouquets as $bouquet) {
            $this->output->writeln($bouquet->render());
        }
    }

}<file_sep><?php
declare(strict_types=1);

namespace Solaing\FlowerBouquets\Tests\Unit\Output;

use PHPUnit\Framework\TestCase;
use Solaing\FlowerBouquets\Aggregates\FlowerBouquetContainer;
use Solaing\FlowerBouquets\Output\GenerateBouquetCollection;
use Solaing\FlowerBouquets\Tests\Factories\BouquetDesignFactory;
use Solaing\FlowerBouquets\Tests\Factories\FlowerFactory;

final class BouquetCollectionHasBeenGeneratedProperlyTest extends TestCase
{
    public final function test_bouquet_collection_is_empty_when_the_container_does_not_have_bouquet_designs() {
        $container = new FlowerBouquetContainer();

        $collection = GenerateBouquetCollection::fromContainer($container);

        $this->assertEmpty($collection);
    }

    public final function test_bouquet_has_the_same_design_name_and_size() {
        $bouquetDesign = BouquetDesignFactory::make();
        $container = new FlowerBouquetContainer;
        $container->addBouquetDesign($bouquetDesign);

        $collection = GenerateBouquetCollection::fromContainer($container);

        $bouquet = $collection[0];
        $this->assertEquals($bouquetDesign->name(), $bouquet->name());
        $this->assertEquals($bouquetDesign->bouquetSize(), $bouquet->flowerSize());
    }

    public final function test_bouquet_is_created_with_bouquet_design_and_exactly_same_flowers() {
        $flower1 = FlowerFactory::make("a", "S", 10);
        $flower2 = FlowerFactory::make("b", "S", 15);
        $flower3 = FlowerFactory::make("c", "S", 10);
        $flowers = [ $flower1, $flower2, $flower3 ];
        $bouquetDesign = BouquetDesignFactory::make($flowers, 35);
        $container = new FlowerBouquetContainer;
        $container->addFlower($flower1);
        $container->addFlower($flower2);
        $container->addFlower($flower3);
        $container->addBouquetDesign($bouquetDesign);

        $collection = GenerateBouquetCollection::fromContainer($container);

        $bouquet = $collection[0];
        $this->assertEquals($flowers, $bouquet->flowers());
        $this->assertEquals(0, $bouquet->totalFlowersLeft());
    }

    public final function test_bouquet_collection_is_created_with_bouquet_design_and_exactly_same_flowers() {
        $flower1 = FlowerFactory::make("a", "S", 10);
        $flower2 = FlowerFactory::make("b", "S", 15);
        $flower3 = FlowerFactory::make("c", "S", 10);
        $flowers = [ $flower1, $flower2, $flower3 ];
        $bouquetDesign = BouquetDesignFactory::make($flowers, 35);
        $container = new FlowerBouquetContainer;
        $container->addFlower($flower1);
        $container->addFlower($flower2);
        $container->addFlower($flower3);
        $container->addBouquetDesign($bouquetDesign);

        $collection = GenerateBouquetCollection::fromContainer($container);

        $bouquet = $collection[0];
        $this->assertEquals($flowers, $bouquet->flowers());
        $this->assertEquals(0, $bouquet->totalFlowersLeft());
    }

    public final function test_bouquet_collection_is_refilled_with_flowers_not_included_in_the_design() {
        $flower1 = FlowerFactory::make("a", "S", 10);
        $flower2 = FlowerFactory::make("b", "S", 15);
        $flower3 = FlowerFactory::make("c", "S", 5);
        $flower4 = FlowerFactory::make("z", "S", 5);
        $flowers = [ $flower1, $flower2, $flower3 ];
        $bouquetDesign = BouquetDesignFactory::make($flowers, 35);
        $container = (new FlowerBouquetContainer);
        $container->addFlower($flower1);
        $container->addFlower($flower2);
        $container->addFlower($flower3);
        $container->addFlower($flower4);
        $container->addBouquetDesign($bouquetDesign);

        $collection = GenerateBouquetCollection::fromContainer($container);

        $bouquet = $collection[0];
        $this->assertEquals([ $flower1, $flower2, $flower3, $flower4 ], $bouquet->flowers());
        $this->assertEquals(0, $bouquet->totalFlowersLeft());
    }

    public final function test_bouquet_is_refilled_but_still_some_flowers_left() {
        $flower1 = FlowerFactory::make("a", "S", 10);
        $flower2 = FlowerFactory::make("b", "S", 15);
        $flower3 = FlowerFactory::make("c", "S", 5);
        $flower4 = FlowerFactory::make("z", "S", 2);
        $flowers = [ $flower1, $flower2, $flower3 ];
        $bouquetDesign = BouquetDesignFactory::make($flowers, 35);
        $container = (new FlowerBouquetContainer);
        $container->addFlower($flower1);
        $container->addFlower($flower2);
        $container->addFlower($flower3);
        $container->addFlower($flower4);
        $container->addBouquetDesign($bouquetDesign);

        $collection = GenerateBouquetCollection::fromContainer($container);

        $bouquet = $collection[0];
        $this->assertEquals(3, $bouquet->totalFlowersLeft());
    }

    public final function test_bouquet_is_refilled_with_any_flower_from_design() {
        $flower1 = FlowerFactory::make("a", "S", 10);
        $flower2 = FlowerFactory::make("b", "S", 15);
        $flower3 = FlowerFactory::make("c", "S", 5);
        $flower4 = FlowerFactory::make("z", "S", 2);
        $flowers = [ $flower1, $flower2 ];
        $bouquetDesign = BouquetDesignFactory::make($flowers, 35);
        $container = (new FlowerBouquetContainer);
        $container->addFlower($flower3);
        $container->addFlower($flower4);
        $container->addBouquetDesign($bouquetDesign);

        $collection = GenerateBouquetCollection::fromContainer($container);

        $bouquet = $collection[0];
        $this->assertEquals([ $flower3, $flower4 ], $bouquet->flowers());
        $this->assertEquals(28, $bouquet->totalFlowersLeft());
    }

    public final function test_bouquet_collection_from_multiple_bouquet_designs() {
        $flower1 = FlowerFactory::make("a", "S", 10);
        $flower2 = FlowerFactory::make("b", "S", 15);
        $flower3 = FlowerFactory::make("c", "S", 5);
        $flower4 = FlowerFactory::make("z", "S", 2);
        $flower5 = FlowerFactory::make("i", "S", 8);

        $bouquetDesign1 = BouquetDesignFactory::make([ $flower1, $flower3 ], 15);
        $bouquetDesign2 = BouquetDesignFactory::make([ $flower2, $flower4 ], 25);

        $container = (new FlowerBouquetContainer);
        $container->addFlower($flower1);
        $container->addFlower($flower2);
        $container->addFlower($flower3);
        $container->addFlower($flower4);
        $container->addFlower($flower5);

        $container->addBouquetDesign($bouquetDesign1);
        $container->addBouquetDesign($bouquetDesign2);


        $collection = GenerateBouquetCollection::fromContainer($container);

        $bouquet1 = $collection[0];
        $this->assertEquals([ $flower1, $flower3 ], $bouquet1->flowers());
        $this->assertEquals(0, $bouquet1->totalFlowersLeft());
        $bouquet2 = $collection[1];
        $this->assertEquals([ $flower2, $flower4, $flower5 ], $bouquet2->flowers());
        $this->assertEquals(0, $bouquet2->totalFlowersLeft());
    }
}<file_sep><?php
declare(strict_types=1);


namespace Solaing\FlowerBouquets\Entities;


final class BouquetDesign
{
    private $name;
    private $bouquetSize;
    private $flowers;
    private $totalFlowers;

    public function __construct(BouquetName $name, BouquetSize $bouquetSize, array $flowers, int $totalFlowers)
    {
        $this->name = $name;
        $this->bouquetSize = $bouquetSize;
        $this->flowers = $flowers;
        $this->totalFlowers = $totalFlowers;
    }

    public static function fromLine(string $stringLine): self
    {
        $chars = str_split($stringLine);
        $name = new BouquetName($chars[0]);
        $flowerSize = new BouquetSize($chars[1]);
        $total = self::extractLastNumberOccurrence($chars);
        $numOfDigitsInTotal = strlen((string)$total);
        $flowerCharsSize = sizeof($chars) - 2 - $numOfDigitsInTotal;
        $flowers = self::extractFlowers(
            array_slice($chars, 2, $flowerCharsSize),
            $flowerSize
        );

        return new self($name, $flowerSize, $flowers, $total);
    }

    private static function extractFlowers(array $chars, BouquetSize $flowerSize): array
    {
        $flowers = [];

        while (sizeof($chars) > 0) {
            $flowerQuantity = self::extractFirstNumberOccurrence($chars);

            $chars = array_slice($chars, strlen((string)$flowerQuantity));
            $flowerSpecie = array_shift($chars);
            $flowers[] = new Flower(
                new FlowerSpecie($flowerSpecie),
                $flowerSize,
                $flowerQuantity
            );
        }

        return $flowers;
    }

    public function name(): string
    {
        return (string)$this->name;
    }

    public function bouquetSize(): string
    {
        return (string)$this->bouquetSize;
    }

    /**
     * @return Flower[]
     */
    public function flowers(): array
    {
        return $this->flowers;
    }

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

    private static function extractFirstNumberOccurrence(array $chars): int
    {
        $total = "";
        foreach ($chars as $char) {
            if (!is_numeric($char)) {
                break;
            }
            $total =  $total . $char;
        }

        return (int)$total;
    }

    private static function extractLastNumberOccurrence(array $chars): int
    {
        $total = "";
        foreach (array_reverse($chars) as $char) {
            if (!is_numeric($char)) {
                break;
            }
            $total =  $char . $total;
        }

        return (int)$total;
    }
}<file_sep><?php
declare(strict_types=1);

namespace Solaing\FlowerBouquets\Tests\Unit\Aggregates;

use PHPUnit\Framework\TestCase;
use Solaing\FlowerBouquets\Aggregates\FlowerBouquetContainer;
use Solaing\FlowerBouquets\Tests\Factories\FlowerFactory;

final class FlowersAreExtractedFromTheContainerTest extends TestCase
{
    public final function test_flower_is_not_extracted_if_is_not_there() {
        $flower1 = FlowerFactory::make("a", "S");
        $flower2 = FlowerFactory::make("b", "L");
        $container = new FlowerBouquetContainer();
        $container->addFlower($flower1);

        $container->extractFlower($flower2);

        $flowerInContainer = $container->flowers()[0];
        $this->assertEquals($flower1->quantity(), $flowerInContainer->quantity());
    }

    public final function test_flower_quantity_is_less_when_flower_is_extracted() {
        $flower1 = FlowerFactory::make("a", "S", 10);
        $flower2 = FlowerFactory::make("a", "S", 7);
        $container = new FlowerBouquetContainer();
        $container->addFlower($flower1);

        $container->extractFlower($flower2);

        $flowerInContainer = $container->flowers()[0];
        $this->assertEquals(3, $flowerInContainer->quantity());
    }

    public final function test_flower_quantity_cannot_be_less_than_0() {
        $flower1 = FlowerFactory::make("a", "S", 10);
        $flower2 = FlowerFactory::make("a", "S", 15);
        $container = new FlowerBouquetContainer();
        $container->addFlower($flower1);

        $container->extractFlower($flower2);

        $flowerInContainer = $container->flowers()[0];
        $this->assertEquals(0, $flowerInContainer->quantity());
    }
}<file_sep><?php
declare(strict_types=1);

namespace Solaing\FlowerBouquets\Tests\Factories;

use Faker\Factory;
use Solaing\FlowerBouquets\Entities\Flower;
use Solaing\FlowerBouquets\Entities\BouquetSize;
use Solaing\FlowerBouquets\Entities\FlowerSpecie;

final class FlowerFactory
{
    public static function make(string $specie = null, string $size = null, int $quantity = null): Flower
    {
        $faker = Factory::create();

        return new Flower(
            new FlowerSpecie($specie ?? strtolower($faker->randomLetter)),
            new BouquetSize($size ?? $faker->randomElement(["S", "L"])),
            $quantity ?? random_int(1, 200)
        );
    }

    public static function withQuantity(int $quantity): Flower
    {
        return self::make(null, null, $quantity);
    }
}<file_sep><?php
declare(strict_types=1);

namespace Solaing\FlowerBouquets\Entities;

use Solaing\FlowerBouquets\Exceptions\InvalidFlowerSpecie;

final class FlowerSpecie
{
    private $name;

    public function __construct(string $name)
    {
        if (!preg_match('/^[a-z]$/', $name)) {
            throw InvalidFlowerSpecie::fromCharacter($name);
        }
        $this->name = $name;
    }

    public function __toString(): string
    {
        return $this->name;
    }
}<file_sep><?php
declare(strict_types=1);


namespace Solaing\FlowerBouquets\Tests\Unit\Aggregates;


use PHPUnit\Framework\TestCase;
use Solaing\FlowerBouquets\Exceptions\TooManyFlowersInTheBouquet;
use Solaing\FlowerBouquets\Tests\Factories\BouquetFactory;
use Solaing\FlowerBouquets\Tests\Factories\FlowerFactory;

final class BouquetCanBeCompletelyFilledTest extends TestCase
{
    public final function test_bouquet_has_the_same_flowers_if_we_add_empty_collection() {
        $bouquet = BouquetFactory::buildWithFlowers([
            FlowerFactory::make(null, null, 1),
            FlowerFactory::make(null, null, 2),
            FlowerFactory::make(null, null, 3)
        ], 10);

        $newBouquet = $bouquet->addMoreFlowers([]);

        $this->assertEquals(3, sizeof($newBouquet->flowers()));
    }

    public final function test_bouquet_has_more_flowers_when_we_add_more() {
        $bouquet = BouquetFactory::buildWithFlowers([
            FlowerFactory::make(null, null, 1),
            FlowerFactory::make(null, null, 1),
            FlowerFactory::make(null, null, 2)
        ], 10);

        $newBouquet = $bouquet->addMoreFlowers([
            FlowerFactory::make(null, null, 1),
            FlowerFactory::make(null, null, 1),
            FlowerFactory::make(null, null,3)
        ]);

        $this->assertEquals(6, sizeof($newBouquet->flowers()));
    }

    public final function test_bouquet_not_allow_to_have_more_flowers_than_total() {
        $bouquet = BouquetFactory::buildWithFlowers([
            FlowerFactory::make(null, null, 1),
            FlowerFactory::make(null, null, 1),
            FlowerFactory::make(null, null, 2)
        ], 10);

        $this->expectException(TooManyFlowersInTheBouquet::class);

        $newBouquet = $bouquet->addMoreFlowers([
            FlowerFactory::make(null, null, 1),
            FlowerFactory::make(null, null, 1),
            FlowerFactory::make(null, null,25)
        ]);
    }
}<file_sep>#!/usr/bin/php
<?php

require __DIR__ . '/vendor/autoload.php';

use Solaing\FlowerBouquets\GenerateBouquets;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;

$input = new ArgvInput();
$input->setStream(fopen(__DIR__.'/resources/' . $input->getFirstArgument(), 'r'));
$output = new ConsoleOutput();

(new GenerateBouquets($input, $output))->exec();

?><file_sep><?php
declare(strict_types=1);


namespace Solaing\FlowerBouquets\Tests\Stubs;


use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class OutputStub implements OutputInterface
{
    private $writtenLines = [];

    public function write($messages, $newline = false, $options = 0)
    {
    }

    public function writeln($messages, $options = 0)
    {
        $this->writtenLines[] = $messages;
    }

    public function setVerbosity($level)
    {
    }

    public function getVerbosity()
    {
    }

    public function isQuiet()
    {
    }


    public function isVerbose()
    {
    }

    public function isVeryVerbose()
    {
    }

    public function isDebug()
    {
    }

    public function setDecorated($decorated)
    {
    }

    public function isDecorated()
    {
    }

    public function setFormatter(OutputFormatterInterface $formatter)
    {
    }

    public function getFormatter()
    {
    }

    public function writtenLines(): array
    {
        return $this->writtenLines;
    }
}<file_sep><?php
declare(strict_types=1);


namespace Solaing\FlowerBouquets\Aggregates;


use Solaing\FlowerBouquets\Entities\BouquetDesign;
use Solaing\FlowerBouquets\Entities\Flower;
use Solaing\FlowerBouquets\Entities\BouquetSize;
use Solaing\FlowerBouquets\Entities\FlowerSpecie;

final class FlowerBouquetContainer
{
    /** @var Flower[] */
    private $flowers = [];
    /** @var BouquetDesign[] */
    private $bouquetDesigns = [];

    public function addFlower(Flower $flower): void
    {
        $flowerInContainer = $this->getSameFlowerFromTheContainer($flower);
        if ($flowerInContainer === null) {
            $this->flowers[] = $flower;
        } else {
            $newFlowerInContainer = $flowerInContainer->increaseQuantity();

            $this->replaceFlowerFromSameSpecie($newFlowerInContainer);
        }
    }

    public function addBouquetDesign(BouquetDesign $bouquetDesign): void
    {
        $this->bouquetDesigns[] = $bouquetDesign;
    }

    public function containsFlower(Flower $flower): bool
    {
        $flower = $this->getSameFlowerFromTheContainer($flower);

        return $flower !== null && $flower->quantity() > 0;
    }

    public function extractFlower(Flower $flower): ?Flower
    {
        foreach ($this->flowers as $key => $flowerInContainer) {
            if ($flowerInContainer->isSameAs($flower)) {
                return $this->extractQuantityFromFlower($flowerInContainer, $flower->quantity(), $key);
            }
        }

        return null;
    }

    /**
     * @param int $quantity
     * @return Flower[]
     */
    public function extractExactQuantityOfFlowers(int $quantity): array
    {
        $flowersToReturn = [];
        $quantityLeft = $quantity;
        foreach ($this->flowers as $key => $flowerInContainer) {
            if ($quantityLeft === 0) {
                break;
            }
            if ($flowerInContainer->quantity() === 0) {
                continue;
            }

            $quantityToExtract = $this->calculateExactQuantityToExtract($quantityLeft, $flowerInContainer);
            $this->extractQuantityFromFlower($flowerInContainer, $quantityToExtract, $key);
            $flowersToReturn[] = new Flower(
                new FlowerSpecie($flowerInContainer->specie()),
                new BouquetSize($flowerInContainer->bouquetSize()),
                $quantityToExtract
            );
            $quantityLeft = max($quantityLeft - $flowerInContainer->quantity(), 0);
        }

        return $flowersToReturn;
    }

    private function getSameFlowerFromTheContainer(Flower $flower): ?Flower
    {
        foreach ($this->flowers as $flowerInContainer) {
            if ($flowerInContainer->isSameAs($flower)) {
                return $flowerInContainer;
            }
        }

        return null;
    }

    private function replaceFlowerFromSameSpecie(Flower $flower): void
    {
        foreach ($this->flowers as $key => $flowerInContainer) {
            if ($flowerInContainer->isSameAs($flower)) {
                $this->flowers[$key] = $flower;
                return;
            }
        }
    }

    /**
     * @return Flower[]
     */
    public function flowers(): array
    {
        return $this->flowers;
    }

    public function bouquetDesigns(): array
    {
        return $this->bouquetDesigns;
    }

    public function extractQuantityFromFlower(Flower $flowerInContainer, int $quantity, int $key): Flower
    {
        $flowerWithQuantityExtracted = $flowerInContainer->extractQuantity($quantity);
        $this->flowers[$key] = $flowerWithQuantityExtracted;

        return $flowerWithQuantityExtracted;
    }

    public function calculateExactQuantityToExtract(int $quantityLeft, Flower $flowerInContainer): int
    {
        $quantityToExtract = $quantityLeft;
        if ($quantityToExtract > $flowerInContainer->quantity()) {
            $quantityToExtract = $flowerInContainer->quantity();
        }

        return $quantityToExtract;
    }
}<file_sep>FROM phpunit/phpunit:latest
MAINTAINER <NAME> <<EMAIL>>

WORKDIR /app
COPY . /app
VOLUME ["/app"]

RUN composer install --no-dev --no-interaction -o<file_sep><?php
declare(strict_types=1);


namespace Solaing\FlowerBouquets\Exceptions;


final class InvalidBouquetName extends \InvalidArgumentException
{
    public static function fromInvalidName(string $name): self
    {
        return new self("The bouquet name '$name'' is invalid");
    }
}<file_sep><?php
declare(strict_types=1);


namespace Solaing\FlowerBouquets\Tests\Integration;


use PHPUnit\Framework\TestCase;
use Solaing\FlowerBouquets\GenerateBouquets;
use Solaing\FlowerBouquets\Tests\Stubs\InputStub;
use Solaing\FlowerBouquets\Tests\Stubs\OutputStub;

final class TheFlowerBouquetsAreGeneratedInTheOutputTest extends TestCase
{
    private const RESOURCES_DIR_PATH = __DIR__ . '/../../resources';

    private $output;
    private $input;

    public function setUp(): void
    {
        parent::setUp();
        $this->output = new OutputStub();
        $this->input = new InputStub();
    }

    public final function test_the_flower_bouquets_title_is_generated() {
        $filePath = self::RESOURCES_DIR_PATH . '/empty_file.txt';
        $this->input->setStream(fopen($filePath, 'r'));
        (new GenerateBouquets($this->input, $this->output))->exec();

        $writtenLines = $this->output->writtenLines();
        $this->assertEquals('----- Flower Bouquets ---', $writtenLines[1]);
    }

    public final function test_the_bouquet_is_rendered_without_flowers() {
        $filePath = self::RESOURCES_DIR_PATH . '/bouquet_design.txt';
        $this->input->setStream(fopen($filePath, 'r'));
        (new GenerateBouquets($this->input, $this->output))->exec();

        $writtenLines = $this->output->writtenLines();
        $this->assertEquals('AL', $writtenLines[3]);
    }

    public final function test_any_bouquet_is_rendered_with_only_flowers() {
        $filePath = self::RESOURCES_DIR_PATH . '/multiple_flowers.txt';
        $this->input->setStream(fopen($filePath, 'r'));
        (new GenerateBouquets($this->input, $this->output))->exec();

        $writtenLines = $this->output->writtenLines();
        $this->assertEquals(3, sizeof($writtenLines));
    }

    public final function test_multiple_bouquets_are_rendered_with_multiple_designs() {
        $filePath = self::RESOURCES_DIR_PATH . '/multiple_bouquet_designs.txt';
        $this->input->setStream(fopen($filePath, 'r'));
        (new GenerateBouquets($this->input, $this->output))->exec();

        $writtenLines = $this->output->writtenLines();
        $this->assertEquals('AL', $writtenLines[3]);
        $this->assertEquals('AS', $writtenLines[4]);
        $this->assertEquals('BL', $writtenLines[5]);
        $this->assertEquals('BS', $writtenLines[6]);
        $this->assertEquals('CL', $writtenLines[7]);
        $this->assertEquals('DL', $writtenLines[8]);
    }

    public final function test_with_the_sample() {
        $filePath = self::RESOURCES_DIR_PATH . '/sample.txt';
        $this->input->setStream(fopen($filePath, 'r'));
        (new GenerateBouquets($this->input, $this->output))->exec();

        $writtenLines = $this->output->writtenLines();
        $this->assertStringContainsString('ALa10b15c5', $writtenLines[3]);
        $this->assertStringContainsString('ASa10b10c5', $writtenLines[4]);
        $this->assertStringContainsString('BLb15c1c5', $writtenLines[5]);
        $this->assertStringContainsString('BSb10c5c1', $writtenLines[6]);
        $this->assertStringContainsString('CLa20c15c10', $writtenLines[7]);
        $this->assertStringContainsString('DLb20c8', $writtenLines[8]);
    }

    public function tearDown(): void
    {
        unset($this->output);
        parent::tearDown();
    }
}<file_sep><?php
declare(strict_types=1);

namespace Solaing\FlowerBouquets\Tests\Unit\Aggregates;

use PHPUnit\Framework\TestCase;
use Solaing\FlowerBouquets\Entities\Flower;
use Solaing\FlowerBouquets\Aggregates\FlowerBouquetContainer;
use Solaing\FlowerBouquets\Tests\Factories\BouquetDesignFactory;
use Solaing\FlowerBouquets\Tests\Factories\FlowerFactory;

final class ContainerStoresTheFlowersAndBouquetsTest extends TestCase
{
    final public function test_the_container_adds_flowers() {
        $flower = FlowerFactory::make();
        $container = new FlowerBouquetContainer();

        $container->addFlower($flower);

        $this->assertEquals($flower, $container->flowers()[0]);
    }

    final public function test_the_container_adds_bouquet_designs() {
        $bouquetDesign = BouquetDesignFactory::make();
        $container = new FlowerBouquetContainer();

        $container->addBouquetDesign($bouquetDesign);

        $this->assertEquals($bouquetDesign, $container->bouquetDesigns()[0]);
    }

    final public function test_the_container_increases_the_quantity_of_flowers_if_is_there() {
        $flower1 = FlowerFactory::make();
        $flower2 = FlowerFactory::make($flower1->specie(), $flower1->bouquetSize());
        $container = new FlowerBouquetContainer();

        $container->addFlower($flower1);
        $container->addFlower($flower2);

        /** @var Flower $flower */
        $flower = $container->flowers()[0];
        $this->assertEquals($flower1->quantity() + 1, $flower->quantity());
    }

    final public function test_the_container_NOT_increase_the_quantity_if_the_specie_is_NOT_there() {
        $flower1 = FlowerFactory::make("a");
        $flower2 = FlowerFactory::make("b");
        $container = new FlowerBouquetContainer();

        $container->addFlower($flower1);
        $container->addFlower($flower2);

        /** @var Flower $flower */
        $flower = $container->flowers()[0];
        $this->assertEquals($flower1->quantity(), $flower->quantity());
    }

    final public function test_the_container_NOT_increase_the_quantity_if_the_size_does_not_match() {
        $flower1 = FlowerFactory::make("a", "S");
        $flower2 = FlowerFactory::make("a", "L");
        $container = new FlowerBouquetContainer();

        $container->addFlower($flower1);
        $container->addFlower($flower2);

        /** @var Flower $flower */
        $flower = $container->flowers()[0];
        $this->assertEquals($flower1->quantity(), $flower->quantity());
    }

    final public function test_container_knows_if_a_flower_exists() {
        $flower1 = FlowerFactory::make();
        $container = new FlowerBouquetContainer();

        $container->addFlower($flower1);

        $this->assertTrue($container->containsFlower($flower1));
    }

    final public function test_container_knows_if_a_flower_NOT_exists() {
        $flower1 = FlowerFactory::make();
        $container = new FlowerBouquetContainer();

        $this->assertFalse($container->containsFlower($flower1));
    }

    final public function test_container_knows_when_there_are_no_more_flowers_left() {
        $flower1 = FlowerFactory::make("a", "S", 0);
        $container = new FlowerBouquetContainer();

        $container->addFlower($flower1);

        $this->assertFalse($container->containsFlower($flower1));
    }
}<file_sep><?php
declare(strict_types=1);


namespace Solaing\FlowerBouquets\Tests\Factories;


use Faker\Factory;
use Solaing\FlowerBouquets\Entities\BouquetDesign;
use Solaing\FlowerBouquets\Entities\BouquetName;
use Solaing\FlowerBouquets\Entities\BouquetSize;

final class BouquetDesignFactory
{
    public static function make(array $flowers = null, int $quantity = null): BouquetDesign
    {
        $faker = Factory::create();
        return new BouquetDesign(
            new BouquetName(strtoupper($faker->randomLetter)),
            new BouquetSize($faker->randomElement(['S', 'L'])),
            $flowers ?? self::generateFlowers(),
            $quantity ?? random_int(1, 200)
        );
    }

    private static function generateFlowers() {
        $flowers = [];
        $numOfInstances = random_int(1, 5);
        while ($numOfInstances > 0) {
            $flowers[] = FlowerFactory::make();
            --$numOfInstances;
        }

        return $flowers;
    }
}<file_sep># Flower Bouquets
This is a console application that generates a collection of flower bouquets based on a set of bouquet design and a set
of flower species.


This application was implemented for a company code challenge. 

## Installation
### Locally
You just need to run `composer` to install the dependencies.
```php
composer install
```
### Using Docker
You just need to build a new docker container using the Dockerfile included in the root folder of the project.
```bash
docker build .
```
Please check the Docker documentation if you want to know more about it -> https://docs.docker.com/engine/reference/commandline/build/
## Usage
This application can be executed using the `run.php` file and including the input stream that the application is going to 
consume in the `/resources` folder. You need to include your input in file and pass the filename as an argument when you execute the PHP file through the console.

Please take a look into this folder, you will find some examples about the input format.

### About the source code
On the other hand, you can see that the class `\Solaing\FlowerBouquets\GenerateBouquets` has injected a `\Symfony\Component\Console\Input\StreamableInputInterface` and a 
`\Symfony\Component\Console\Output\OutputInterface`. 

It makes this code completely customizable with any kind of input or output that implements those interfaces.

I suggest you to take a look into the [Symfony Console](https://symfony.com/doc/current/components/console.html) package to get some ideas of how you can run this class.
## Running the application
Dont' forget to add your input file before you run the application ;)
### Locally
Simply run in the root folder using the php binary.
```php
php -f run.php <YOUR INPUT FILE>.txt
```
### Using Docker
Same idea but we need to execute the command in the container and redirect the std output.
```php
docker exec -it <CONTAINER ID OR NAME> bash -c "php -f run.php <YOUR INPUT FILE>.txt 2>&1"
```
## Running Tests
This application is completely tested to be sure everything works fine and to give the reader some help to understand 
the implementation.
### Locally
Simply run the PHPUnit binary located in the `vendor` folder.
```php
vendor/bin/phpunit
```
### Using Docker
Same idea but we need to execute the command in the container and redirect the std output.
```php
docker exec -it <CONTAINER ID OR NAME> bash -c "vendor/bin/phpunit 2>&1"
```<file_sep><?php
declare(strict_types=1);


namespace Solaing\FlowerBouquets\Exceptions;


use InvalidArgumentException;

final class InvalidFlowerSpecie extends InvalidArgumentException
{
    public static function fromCharacter(string $specie): self
    {
        return new self("The flower specie '$specie'' is invalid");
    }
}<file_sep><?php
declare(strict_types=1);

namespace Solaing\FlowerBouquets\Tests\Unit\Entities;

use PHPUnit\Framework\TestCase;
use Solaing\FlowerBouquets\Entities\BouquetDesign;
use Solaing\FlowerBouquets\Entities\Flower;

final class BouquetDesignIsGeneratedFromStringLineTest extends TestCase
{
    public final function test_bouquet_design_contains_the_correct_name()
    {
        $bouquetDesign = BouquetDesign::fromLine("AS3a4b6k20");

        $this->assertEquals("A", $bouquetDesign->name());
    }

    public final function test_bouquet_design_contains_the_correct_flower_size()
    {
        $bouquetDesign = BouquetDesign::fromLine("AS3a4b6k20");

        $this->assertEquals("S", $bouquetDesign->bouquetSize());
    }

    public final function test_bouquet_design_contains_the_correct_total()
    {
        $bouquetDesign = BouquetDesign::fromLine("AS3a4b6k345667");

        $this->assertEquals(345667, $bouquetDesign->totalFlowers());
    }

    public final function test_bouquet_design_contains_the_correct_flower()
    {
        $bouquetDesign = BouquetDesign::fromLine("AS3a20");

        /** @var Flower $flower */
        $flower = $bouquetDesign->flowers()[0];
        $this->assertEquals("a", $flower->specie());
        $this->assertEquals("S", $flower->bouquetSize());
        $this->assertEquals(3, $flower->quantity());
    }

    public final function test_bouquet_design_contains_the_correct_number_of_flowers()
    {
        $bouquetDesign = BouquetDesign::fromLine("AS3a4b6k20");

        $flowers = $bouquetDesign->flowers();
        /** @var Flower $flower */
        $flower = $flowers[0];
        $this->assertEquals("a", $flower->specie());
        $this->assertEquals("S", $flower->bouquetSize());
        $this->assertEquals(3, $flower->quantity());
        $flower = $flowers[1];
        $this->assertEquals("b", $flower->specie());
        $this->assertEquals("S", $flower->bouquetSize());
        $this->assertEquals(4, $flower->quantity());
        $flower = $flowers[2];
        $this->assertEquals("k", $flower->specie());
        $this->assertEquals("S", $flower->bouquetSize());
        $this->assertEquals(6, $flower->quantity());
    }

    public final function test_bouquet_design_contains_flowers_with_more_than_one_digit_in_the_quantity()
    {
        $bouquetDesign = BouquetDesign::fromLine("AS30a44b6k20");

        $flowers = $bouquetDesign->flowers();
        /** @var Flower $flower */
        $flower = $flowers[0];
        $this->assertEquals(30, $flower->quantity());
        $flower = $flowers[1];
        $this->assertEquals(44, $flower->quantity());

    }

}<file_sep><?php
declare(strict_types=1);


namespace Solaing\FlowerBouquets\Entities;


use Solaing\FlowerBouquets\Exceptions\InvalidBouquetSize;

final class BouquetSize
{
    private $size;

    /**
     * @param string $size
     *
     * @throws InvalidBouquetSize
     */
    public function __construct(string $size)
    {
        if (!preg_match('/^(S|L)$/', $size)) {
            throw InvalidBouquetSize::fromCharacter($size);
        }
        $this->size = $size;
    }

    public function __toString(): string
    {
        return $this->size;
    }
}<file_sep><?php
declare(strict_types=1);


namespace Solaing\FlowerBouquets\Aggregates;


use Solaing\FlowerBouquets\Entities\BouquetName;
use Solaing\FlowerBouquets\Entities\Flower;
use Solaing\FlowerBouquets\Entities\BouquetSize;
use Solaing\FlowerBouquets\Exceptions\TooManyFlowersInTheBouquet;

final class Bouquet
{
    private $name;
    private $flowerSize;
    /** @var Flower[] */
    private $flowers;
    /**
     * @var int
     */
    private $totalFlowers;

    public function __construct(BouquetName $name, BouquetSize $flowerSize, array $flowers, int $totalFlowers)
    {
        $this->name = $name;
        $this->flowerSize = $flowerSize;
        $this->flowers = $flowers;
        $this->totalFlowers = $totalFlowers;
    }

    public function name(): string
    {
        return (string)$this->name;
    }

    public function flowerSize(): BouquetSize
    {
        return $this->flowerSize;
    }

    public function flowers(): array
    {
        return $this->flowers;
    }

    public function totalFlowersLeft(): int
    {
        $totalQuantityOfFlowers = $this->getTotalQuantityOfFlowers($this->flowers);

        return max($this->totalFlowers - $totalQuantityOfFlowers, 0);
    }

    /**
     * @param array $flowers
     * @return $this
     *
     * @throws TooManyFlowersInTheBouquet
     */
    public function addMoreFlowers(array $flowers): self
    {
        $totalQuantityOfFlowers = $this->mergeTotalQuantityOfFlowers($flowers);
        if ($totalQuantityOfFlowers > $this->totalFlowers) {
            throw TooManyFlowersInTheBouquet::withQuantities(
                $this->name(),
                $this->totalFlowers,
                $totalQuantityOfFlowers
            );
        }
        $newBouquet = clone $this;
        $newBouquet->flowers = array_merge($newBouquet->flowers, $flowers);

        return $newBouquet;
    }

    public function render(): string
    {
        $output = $this->name . $this->flowerSize();
        foreach ($this->flowers as $flower) {
            $output .= $flower->render();
        }

        return $output;
    }

    private function getTotalQuantityOfFlowers(array $flowers): int
    {
        if (sizeof($flowers) === 0) {
            return 0;
        }
        return array_reduce($flowers, function ($totalFlowers, Flower $flower) {
            return $totalFlowers + $flower->quantity();
        });
    }


    public function mergeTotalQuantityOfFlowers(array $flowers): int
    {
        return $this->getTotalQuantityOfFlowers($this->flowers) + $this->getTotalQuantityOfFlowers($flowers);
    }
}<file_sep><?php
declare(strict_types=1);

namespace Solaing\FlowerBouquets\Tests\Stubs;

use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\StreamableInputInterface;

final class InputStub implements StreamableInputInterface
{
    private $stream;

    public function setStream($stream)
    {
        $this->stream = $stream;
    }

    public function getStream()
    {
        return $this->stream;
    }

    public function getFirstArgument()
    {
    }

    public function hasParameterOption($values, $onlyParams = false)
    {
    }

    public function getParameterOption($values, $default = false, $onlyParams = false)
    {
    }


    public function bind(InputDefinition $definition)
    {
    }

    public function validate()
    {
    }

    public function getArguments()
    {
    }

    public function getArgument($name)
    {
    }

    public function setArgument($name, $value)
    {
    }

    public function hasArgument($name)
    {
    }

    public function getOptions()
    {
    }

    public function getOption($name)
    {
    }

    public function setOption($name, $value)
    {
    }

    public function hasOption($name)
    {
    }

    public function isInteractive()
    {
    }

    public function setInteractive($interactive)
    {
    }
}<file_sep><?php
declare(strict_types=1);

namespace Solaing\FlowerBouquets\Tests\Unit\Input;

use PHPUnit\Framework\TestCase;
use Solaing\FlowerBouquets\Input\GenerateFlowerBouquetContainer;

final class FlowerBouquetContainerIsBeingGeneratedFromFileTest extends TestCase
{
    private const RESOURCES_DIR_PATH = __DIR__ . '/../../../resources';

    public final function test_container_is_not_filled_when_there_is_an_empty_line() {
        $streamResource = fopen(self::RESOURCES_DIR_PATH . '/empty_file.txt', 'r');
        $container = GenerateFlowerBouquetContainer::fromResource($streamResource);

        $this->assertEmpty($container->flowers());
        $this->assertEmpty($container->bouquetDesigns());
    }

    public final function test_container_is_filled_with_a_flower() {
        $streamResource = fopen(self::RESOURCES_DIR_PATH . '/one_flower.txt', 'r');
        $container = GenerateFlowerBouquetContainer::fromResource($streamResource);

        $this->assertEquals(1, sizeof($container->flowers()));
    }

    public final function test_container_is_filled_with_a_multiple_flowers() {
        $streamResource = fopen(self::RESOURCES_DIR_PATH . '/multiple_flowers.txt', 'r');
        $container = GenerateFlowerBouquetContainer::fromResource($streamResource);

        $this->assertEquals(5, sizeof($container->flowers()));
    }

    public final function test_container_is_filled_with_a_the_wrong_flowers() {
        $streamResource = fopen(self::RESOURCES_DIR_PATH . '/wrong_flowers.txt', 'r');
        $container = GenerateFlowerBouquetContainer::fromResource($streamResource);

        $this->assertEquals(3, sizeof($container->flowers()));
    }

    public final function test_container_is_filled_with_a_bouquet_design() {
        $streamResource = fopen(self::RESOURCES_DIR_PATH . '/bouquet_design.txt', 'r');
        $container = GenerateFlowerBouquetContainer::fromResource($streamResource);

        $this->assertEquals(1, sizeof($container->bouquetDesigns()));
    }

    public final function test_container_is_filled_with_multiple_bouquet_designs() {
        $streamResource = fopen(self::RESOURCES_DIR_PATH . '/multiple_bouquet_designs.txt', 'r');
        $container = GenerateFlowerBouquetContainer::fromResource($streamResource);

        $this->assertEquals(6, sizeof($container->bouquetDesigns()));
    }

}<file_sep><?php
declare(strict_types=1);


namespace Solaing\FlowerBouquets\Input;


use Solaing\FlowerBouquets\Entities\BouquetDesign;
use Solaing\FlowerBouquets\Entities\Flower;
use Solaing\FlowerBouquets\Aggregates\FlowerBouquetContainer;

final class GenerateFlowerBouquetContainer
{
    private function __construct()
    {
    }

    public static function fromResource($streamResource): FlowerBouquetContainer
    {
        $container = new FlowerBouquetContainer();

        while (!feof($streamResource)) {
            $stringLine = trim((string)fgetss($streamResource));
            if (self::isEmptyLine($stringLine)) {
                continue;
            }
            if (self::isFormattedForFlower($stringLine)) {
                $container->addFlower(Flower::fromLine($stringLine));
                continue;
            }
            if (self::isFormattedForBouquetDesign($stringLine)) {
                $container->addBouquetDesign(BouquetDesign::fromLine($stringLine));
            }
        }

        fclose($streamResource);

        return $container;
    }

    private static function isEmptyLine(string $line): bool
    {
        return empty($line);
    }

    private static function isFormattedForFlower(string $line): bool
    {
        return (bool)preg_match('/^[a-z](S|L)$/', $line);
    }

    private static function isFormattedForBouquetDesign(string $line): bool
    {
        return (bool)preg_match('/^[A-Z](S|L)(.*)([0-9]+)$/', $line);
    }
}<file_sep><?php
declare(strict_types=1);

namespace Solaing\FlowerBouquets\Tests\Unit\Aggregates;

use PHPUnit\Framework\TestCase;
use Solaing\FlowerBouquets\Tests\Factories\BouquetFactory;
use Solaing\FlowerBouquets\Tests\Factories\FlowerFactory;

final class BouquetIsRenderedWithCorrectFormatTest extends TestCase
{
    final public function test_when_bouquet_has_no_flowers() {
        $bouquet = BouquetFactory::buildWithNameAndSize('A', 'S');

        $output = $bouquet->render();

        $this->assertEquals('AS', $output);
    }

    final public function test_when_bouquet_has_some_flowers() {
        $flowers = [
            FlowerFactory::make('a', 'S', 5),
            FlowerFactory::make('b', 'S', 7),
            FlowerFactory::make('c', 'S', 9),
        ];
        $bouquet = BouquetFactory::build('A', 'S', $flowers, 30);

        $output = $bouquet->render();

        $this->assertEquals('ASa5b7c9', $output);
    }
}<file_sep><?php
declare(strict_types=1);

namespace Solaing\FlowerBouquets\Entities;

use Solaing\FlowerBouquets\Exceptions\InvalidBouquetName;

final class BouquetName
{
    private $name;

    /**
     * @param string $name
     *
     * @throws InvalidBouquetName
     */
    public function __construct(string $name)
    {
        if (!preg_match('/^[A-Z]$/', $name)) {
            throw InvalidBouquetName::fromInvalidName($name);
        }
        $this->name = $name;
    }

    public function __toString(): string
    {
        return $this->name;
    }
}<file_sep><?php
declare(strict_types=1);

namespace Solaing\FlowerBouquets\Output;

use Solaing\FlowerBouquets\Aggregates\Bouquet;
use Solaing\FlowerBouquets\Entities\BouquetDesign;
use Solaing\FlowerBouquets\Entities\BouquetName;
use Solaing\FlowerBouquets\Aggregates\FlowerBouquetContainer;
use Solaing\FlowerBouquets\Entities\BouquetSize;

final class GenerateBouquetCollection
{
    private function __construct()
    {
    }

    /**
     * @param FlowerBouquetContainer $container
     * @return Bouquet[]
     */
    public static function fromContainer(FlowerBouquetContainer $container): array
    {
        /** @var Bouquet[] $bouquets */
        $bouquets = [];
        /** @var BouquetDesign[] $bouquetDesigns */
        $bouquetDesigns = $container->bouquetDesigns();
        foreach ($bouquetDesigns as $bouquetDesign) {
            $bouquets[] = self::generateBouquet($bouquetDesign, $container);
        }

        foreach ($bouquets as $key => $bouquet) {
            $totalFlowersLeft = $bouquet->totalFlowersLeft();
            if ($totalFlowersLeft > 0) {
                $bouquets[$key] = self::refillBouquet($container, $totalFlowersLeft, $bouquet);
            }
        }

        return $bouquets;
    }

    private static function generateBouquet(BouquetDesign $bouquetDesign, FlowerBouquetContainer $container): Bouquet
    {
        $flowersInBouquet = [];
        foreach ($bouquetDesign->flowers() as $flower) {
            if (null === $container->extractFlower($flower)) {
                continue;
            }
            $flowersInBouquet[] = $flower;
        }

        return new Bouquet(
            new BouquetName($bouquetDesign->name()),
            new BouquetSize($bouquetDesign->bouquetSize()),
            $flowersInBouquet,
            $bouquetDesign->totalFlowers()
        );
    }

    private static function refillBouquet(FlowerBouquetContainer $container, int $totalFlowersLeft, Bouquet $bouquet): Bouquet
    {
        $flowersFromContainer = $container->extractExactQuantityOfFlowers($totalFlowersLeft);

        return $bouquet->addMoreFlowers($flowersFromContainer);
    }
}<file_sep><?php
declare(strict_types=1);


namespace Solaing\FlowerBouquets\Tests\Unit\Aggregates;


use PHPUnit\Framework\TestCase;
use Solaing\FlowerBouquets\Tests\Factories\BouquetFactory;
use Solaing\FlowerBouquets\Tests\Factories\FlowerFactory;

final class BouquetControlsTheNumFlowersTest extends TestCase
{
    public final function test_when_there_are_no_flowers() {
        $bouquet = BouquetFactory::buildWithFlowers([], 10);

        $this->assertEquals(10, $bouquet->totalFlowersLeft());
    }

    public final function test_when_the_are_some_flowers_left() {
        $bouquet = BouquetFactory::buildWithFlowers([
            FlowerFactory::make("a", "S", 2),
            FlowerFactory::make("b", "S", 1),
            FlowerFactory::make("s", "S", 3)
        ], 10);

        $this->assertEquals(4, $bouquet->totalFlowersLeft());
    }

    public final function test_when_there_are_no_flowers_left() {
        $bouquet = BouquetFactory::buildWithFlowers([
            FlowerFactory::make("a", "S", 2),
            FlowerFactory::make("b", "S", 1),
            FlowerFactory::make("s", "S", 7)
        ], 10);

        $this->assertEquals(0, $bouquet->totalFlowersLeft());
    }

    public final function test_cannot_be_less_than_0_flowers_left() {
        $bouquet = BouquetFactory::buildWithFlowers([
            FlowerFactory::make("a", "S", 2),
            FlowerFactory::make("b", "S", 4),
            FlowerFactory::make("s", "S", 7)
        ], 10);

        $this->assertEquals(0, $bouquet->totalFlowersLeft());
    }
}<file_sep><?php
declare(strict_types=1);


namespace Solaing\FlowerBouquets\Entities;


final class Flower
{
    private $specie;
    private $bouquetSize;
    private $quantity;

    public function __construct(FlowerSpecie $specie, BouquetSize $size, int $quantity)
    {
        $this->specie = $specie;
        $this->bouquetSize = $size;
        $this->quantity = $quantity;
    }

    public static function fromLine(string $stringLine): self
    {
        $chars = str_split($stringLine);

        return new self(new FlowerSpecie($chars[0]), new BouquetSize($chars[1]), 1);
    }

    public function increaseQuantity(): self {
        $newFlower = clone $this;
        ++$newFlower->quantity;

        return $newFlower;
    }

    public function extractQuantity(int $quantity): self {
        $newFlower = clone $this;

        if ($newFlower->quantity() < $quantity) {
            $newFlower->quantity = 0;
        } else {
            $newFlower->quantity = $newFlower->quantity - $quantity;
        }

        return $newFlower;
    }

    public function specie(): string
    {
        return (string)$this->specie;
    }

    public function bouquetSize(): string
    {
        return (string)$this->bouquetSize;
    }

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

    public function isSameAs(Flower $flower): bool
    {
        return $this->specie() === $flower->specie() && $this->bouquetSize() === $flower->bouquetSize();
    }

    public function render(): string
    {
        return $this->specie() . $this->quantity();
    }
}<file_sep><?php
declare(strict_types=1);


namespace Solaing\FlowerBouquets\Exceptions;


final class TooManyFlowersInTheBouquet extends \InvalidArgumentException
{
    public static function withQuantities(string $bouquetName, int $expected, int $actual): self
    {
        return new self("The Bouquet $bouquetName can contain only $expected flowers, but received $actual.");
    }
}<file_sep><?php
declare(strict_types=1);

namespace Solaing\FlowerBouquets\Tests\Unit\Entities;

use PHPUnit\Framework\TestCase;
use Solaing\FlowerBouquets\Entities\Flower;
use Solaing\FlowerBouquets\Exceptions\InvalidBouquetSize;
use Solaing\FlowerBouquets\Exceptions\InvalidFlowerSpecie;

final class FlowerIsGeneratedFromStringLineTest extends TestCase
{
    final public function test_flower_is_generated_with_the_proper_format() {

        $flower = Flower::fromLine("aL");

        $this->assertEquals("a", $flower->specie());
        $this->assertEquals("L", $flower->bouquetSize());
        $this->assertEquals(1, $flower->quantity());
    }

    final public function test_flower_is_not_generated_with_the_wrong_size() {
        $this->expectException(InvalidBouquetSize::class);

        Flower::fromLine("aK");
    }

    final public function test_flower_is_not_generated_with_the_wrong_specie() {
        $this->expectException(InvalidFlowerSpecie::class);

        Flower::fromLine("WL");
    }
}<file_sep><?php
declare(strict_types=1);

namespace Solaing\FlowerBouquets\Tests\Unit\Aggregates;

use PHPUnit\Framework\TestCase;
use Solaing\FlowerBouquets\Aggregates\FlowerBouquetContainer;
use Solaing\FlowerBouquets\Tests\Factories\FlowerFactory;

final class ContainerExtractsExactQuantityOfFlowersTest extends TestCase
{
    public final function test_when_the_bouquet_has_no_flowers() {
        $container = new FlowerBouquetContainer();

        $this->assertEmpty($container->extractExactQuantityOfFlowers(10));
    }

    public final function test_when_flower_has_more_quantity() {
        $container = new FlowerBouquetContainer();
        $flower = FlowerFactory::withQuantity(15);
        $container->addFlower($flower);

        $flowers = $container->extractExactQuantityOfFlowers(10);
        $this->assertEquals(1, sizeof($flowers));
        $flowerInContainer = $container->flowers()[0];
        $this->assertEquals(5, $flowerInContainer->quantity());
    }

    public final function test_when_flower_has_less_quantity() {
        $container = new FlowerBouquetContainer();
        $flower = FlowerFactory::withQuantity(3);
        $container->addFlower($flower);

        $flowers = $container->extractExactQuantityOfFlowers(10);
        $this->assertEquals(1, sizeof($flowers));
        $flowerInContainer = $container->flowers()[0];
        $this->assertEquals(0, $flowerInContainer->quantity());
    }

    public final function test_flowers_with_quantity_0_are_not_included() {
        $container = new FlowerBouquetContainer();
        $flower1 = FlowerFactory::withQuantity(0);
        $flower2 = FlowerFactory::withQuantity(5);
        $container->addFlower($flower1);
        $container->addFlower($flower2);

        $flowers = $container->extractExactQuantityOfFlowers(10);
        $this->assertEquals(1, sizeof($flowers));
    }

    public final function test_extracted_flower_has_the_quantity_requested() {
        $container = new FlowerBouquetContainer();
        $flower1 = FlowerFactory::withQuantity(10);
        $container->addFlower($flower1);

        $flowers = $container->extractExactQuantityOfFlowers(5);
        $flowerExtracted = $flowers[0];
        $this->assertEquals(5, $flowerExtracted->quantity());
    }
}