<?php

declare(strict_types=1);

namespace App\Radio;

use App\Entity;
use App\Exception\NotFoundException;
use Psr\Container\ContainerInterface;

/**
 * Manager class for radio adapters.
 */
class Adapters
{
    public const FRONTEND_ICECAST = 'icecast';
    public const FRONTEND_SHOUTCAST = 'shoutcast2';
    public const FRONTEND_REMOTE = 'remote';

    public const BACKEND_LIQUIDSOAP = 'liquidsoap';
    public const BACKEND_NONE = 'none';

    public const REMOTE_SHOUTCAST1 = 'shoutcast1';
    public const REMOTE_SHOUTCAST2 = 'shoutcast2';
    public const REMOTE_ICECAST = 'icecast';
    public const REMOTE_AZURARELAY = 'azurarelay';

    public function __construct(
        protected ContainerInterface $adapters
    ) {
    }

    /**
     * @param Entity\Station $station
     *
     * @throws NotFoundException
     */
    public function getFrontendAdapter(Entity\Station $station): Frontend\AbstractFrontend
    {
        $adapters = $this->listFrontendAdapters();

        $frontend_type = $station->getFrontendType();

        if (!isset($adapters[$frontend_type])) {
            throw new NotFoundException('Adapter not found: ' . $frontend_type);
        }

        $class_name = $adapters[$frontend_type]['class'];

        if ($this->adapters->has($class_name)) {
            return $this->adapters->get($class_name);
        }

        throw new NotFoundException('Adapter not found: ' . $class_name);
    }

    /**
     * @param bool $check_installed
     *
     * @return mixed[]
     */
    public function listFrontendAdapters(bool $check_installed = false): array
    {
        $adapters = [
            self::FRONTEND_ICECAST => [
                'name'  => 'Icecast 2.4',
                'class' => Frontend\Icecast::class,
            ],
            self::FRONTEND_SHOUTCAST => [
                'name'  => 'SHOUTcast DNAS 2',
                'class' => Frontend\SHOUTcast::class,
            ],
            self::FRONTEND_REMOTE => [
                'name'  => 'Remote',
                'class' => Frontend\Remote::class,
            ],
        ];

        if ($check_installed) {
            return array_filter(
                $adapters,
                function ($adapter_info) {
                    /** @var AbstractAdapter $adapter */
                    $adapter = $this->adapters->get($adapter_info['class']);
                    return $adapter->isInstalled();
                }
            );
        }

        return $adapters;
    }

    /**
     * @param Entity\Station $station
     *
     * @throws NotFoundException
     */
    public function getBackendAdapter(Entity\Station $station): Backend\AbstractBackend
    {
        $adapters = $this->listBackendAdapters();

        $backend_type = $station->getBackendType();

        if (!isset($adapters[$backend_type])) {
            throw new NotFoundException('Adapter not found: ' . $backend_type);
        }

        $class_name = $adapters[$backend_type]['class'];

        if ($this->adapters->has($class_name)) {
            return $this->adapters->get($class_name);
        }

        throw new NotFoundException('Adapter not found: ' . $class_name);
    }

    /**
     * @param bool $check_installed
     *
     * @return mixed[]
     */
    public function listBackendAdapters(bool $check_installed = false): array
    {
        $adapters = [
            self::BACKEND_LIQUIDSOAP => [
                'name'  => 'Liquidsoap',
                'class' => Backend\Liquidsoap::class,
            ],
            self::BACKEND_NONE => [
                'name'  => 'Disabled',
                'class' => Backend\None::class,
            ],
        ];

        if ($check_installed) {
            return array_filter(
                $adapters,
                function ($adapter_info) {
                    /** @var AbstractAdapter $adapter */
                    $adapter = $this->adapters->get($adapter_info['class']);
                    return $adapter->isInstalled();
                }
            );
        }

        return $adapters;
    }

    /**
     * @param Entity\Station $station
     *
     * @return Remote\AdapterProxy[]
     * @throws NotFoundException
     */
    public function getRemoteAdapters(Entity\Station $station): array
    {
        $remote_adapters = [];

        foreach ($station->getRemotes() as $remote) {
            $remote_adapters[] = new Remote\AdapterProxy($this->getRemoteAdapter($station, $remote), $remote);
        }

        return $remote_adapters;
    }

    /**
     * Assemble an array of ready-to-operate
     *
     * @param Entity\Station $station
     * @param Entity\StationRemote $remote
     *
     * @throws NotFoundException
     */
    public function getRemoteAdapter(Entity\Station $station, Entity\StationRemote $remote): Remote\AbstractRemote
    {
        $adapters = $this->listRemoteAdapters();

        $remote_type = $remote->getType();

        if (!isset($adapters[$remote_type])) {
            throw new NotFoundException('Adapter not found: ' . $remote_type);
        }

        $class_name = $adapters[$remote_type]['class'];

        if ($this->adapters->has($class_name)) {
            return $this->adapters->get($class_name);
        }

        throw new NotFoundException('Adapter not found: ' . $class_name);
    }

    /**
     * @return mixed[]
     */
    public function listRemoteAdapters(): array
    {
        return [
            self::REMOTE_SHOUTCAST1 => [
                'name' => 'SHOUTcast 1',
                'class' => Remote\SHOUTcast1::class,
            ],
            self::REMOTE_SHOUTCAST2 => [
                'name' => 'SHOUTcast 2',
                'class' => Remote\SHOUTcast2::class,
            ],
            self::REMOTE_ICECAST => [
                'name' => 'Icecast',
                'class' => Remote\Icecast::class,
            ],
            self::REMOTE_AZURARELAY => [
                'name' => 'AzuraRelay',
                'class' => Remote\AzuraRelay::class,
            ],
        ];
    }
}
