<?php

require_once __DIR__ . '/time.php';
// TODO Will be discovered by reading directories
require __DIR__ . '/providers/open-nre.php';
require __DIR__ . '/providers/transport-api.php';
require __DIR__ . '/test/providers/open-nre.php';
require __DIR__ . '/providers/tfl.php';

class Director
{
    public static function DepArrBoard(string $loc, string $filterLoc, bool $isArrivals, $at = null, $searchOptions = null)
    {
        assert($at === null || $at instanceof DateTime || $at instanceof string, 'DepArrBoard parameter type: at');
        assert($searchOptions === null || $searchOptions instanceof string || is_array($searchOptions), 'DepArrBoard parameter type: searchOptions');

        $combinedSearchOptions = self::combined_search_options($loc, $searchOptions);

        try {
            // FIXME A better way to handle user-forced simple CRS codes for NRE
            if (strpos($loc, '.') === false) $loc = 'gb.nr.crs.' . $loc;
            if (isset($filterLoc) && $filterLoc !== '' && strpos($filterLoc, '.') === false) $filterLoc = 'gb.nr.crs.' . $filterLoc;

            if (!isset($at) || is_string($at)) {
                $atDT = new DateTime($at ?? "now", Locations::get_timezone_for_loc($loc));
            } else {
                $atDT = $at;
            }

            $provider = self::provider_for($loc);
            if ($provider !== false) {
                $providerInstance = new $provider;
                return $providerInstance->ArrivalOrDepartureBoard($loc, $filterLoc, $isArrivals, $atDT, $combinedSearchOptions);
            }
        } catch (Exception $e) {
            debugInlineIfTestpilot($e);
        }

        return false;
    }

    public static function ConnectionsBoard(string $loc, string $serviceId = null, string $scheduledArrival = null, int $arrivalBoardIndex = null, $searchOptions = null)
    {
        assert($searchOptions === null || $searchOptions instanceof string || is_array($searchOptions), 'DepArrBoard parameter type: searchOptions');

        $combinedSearchOptions = self::combined_search_options($loc, $searchOptions);

        try {
            // FIXME A better way to handle user-forced simple CRS codes for NRE
            if (strpos($loc, '.') === false) $loc = 'gb.nr.crs.' . $loc;

            $scheduledArrivalDT = isset($scheduledArrival) ? new DateTime($scheduledArrival, Locations::get_timezone_for_loc($loc)) : null;

            $provider = self::provider_for($loc);
            if ($provider !== false) {
                $providerInstance = new $provider;
                return $providerInstance->ConnectionsBoard($loc, $serviceId, $scheduledArrivalDT, $arrivalBoardIndex, $combinedSearchOptions);
            }
        } catch (Exception $e) {
            debugInlineIfTestpilot($e);
        }

        return false;
    }

    public static function ServiceDetails(string $serviceId, string $locOfInterest = null, string $atLocOfInterest = null, $searchOptions = null)
    {
        assert($searchOptions === null || $searchOptions instanceof string || is_array($searchOptions), 'DepArrBoard parameter type: searchOptions');

        $combinedSearchOptions = self::combined_search_options($serviceId, $searchOptions);

        try {
            $atLocOfInterestDT = isset($locOfInterest, $atLocOfInterest) ? new DateTime($atLocOfInterest, Locations::get_timezone_for_loc($locOfInterest)) : null;

            $provider = self::provider_for($serviceId);
            if ($provider !== false) {
                $providerInstance = new $provider;
                return $providerInstance->ServiceDetails($serviceId, $locOfInterest, $atLocOfInterestDT, $combinedSearchOptions);
            }
        } catch (Exception $e) {
            debugInlineIfTestpilot($e);
        }
        return false;
    }

    public static function provider_for(string $genericId)
    {
        $bestProvider = FALSE;
        $idLengthMatched = 0;

        foreach (self::$PROVIDERS as $provider) {
            foreach ($provider::SupportedRealms() as $supportedRealm) {
                if (strpos($genericId, $supportedRealm) === 0) {
                    if (strlen($supportedRealm) > $idLengthMatched) {
                        $bestProvider = $provider;
                        $idLengthMatched = strlen($supportedRealm);
                    }
                }
            }
        }

        return $bestProvider;
    }

    public static function realm_for(string $entity)
    {
        // FIXME Needs to be second-last dot?  Or something else involving the realms we know are supported? Works but flaky.
        $dotPos = isset($entity) ? strrpos($entity, '.') : false;
        return $dotPos === false ? false : substr($entity, 0, $dotPos);
    }

    public static function provider_id_for(string $entity)
    {
        $dotPos = isset($entity) ? strrpos($entity, '.') : false;
        return $dotPos === false ? false : strtoupper(substr($entity, $dotPos + 1));
    }

    public static function generic_id_for(string $realm, string $codingScheme, string $providerId)
    {
        return $realm . '.' . $codingScheme . '.' . $providerId;
    }

    public static function is_test_realm(string $entity)
    {
        return isset($entity) && (strpos($entity, 'test.') === 0);
    }

    private static function search_options_for_realm(string $realm, $searchOptions)
    {
        $searchOptions = ensure_is_array($searchOptions);
        $providerSearchOptions = [];

        if (isset($searchOptions)) {
            foreach ($searchOptions as $searchOption) {
                if ($realm === self::realm_for($searchOption)) {
                    $providerSearchOptions[] = self::provider_id_for($searchOption);
                }
            }
        }
        return $providerSearchOptions;
    }

    private static function combined_search_options($loc, $searchOptions = null)
    {
        global $userConfig;

        $searchOptions = ensure_is_array($searchOptions);
        if (isset($userConfig['SearchOpt'])) {
            $searchOptions = array_merge($userConfig['SearchOpt'], $searchOptions);
        }

        $provider = self::provider_for($loc);
        if ($provider !== false) {
            $providerSearchOptions = self::search_options_for_realm($provider::HomeRealm(), $searchOptions);
            if ($providerSearchOptions) {
                $searchOptions = array_merge($providerSearchOptions, $searchOptions);
            }
        }

        return $searchOptions;
    }

    // Test (where available) and live providers
    // TODO Will be discovered by reading directories
    private static $PROVIDERS = [GB_NRE::class, GB_NRE_Test::class, GB_TransportAPI::class, GB_TransportForLondon::class];
}
