<?php

require_once __DIR__ . '/../time.php';
require_once __DIR__ . '/../model.php';
require_once __DIR__ . '/../../view/debug.php';
require_once __DIR__ . '/common.php';
require __DIR__ . '/../lib/TransportAPI.php';
require_once __DIR__ . '/../../locations/locations.php';

class GB_TransportAPI extends ProviderCommon implements RealTimeProvider
{
    protected static $HOME_REALM = 'gb.gov.atco';

    public static function SourceName()
    {
        return "transportapi.com";
    }

    public static function HomeRealm()
    {
        return self::$HOME_REALM;
    }

    public static function SupportedRealms()
    {
        return [self::$HOME_REALM];
    }

    public static function Capabilities()
    {
        return new InformationProviderCapabilities(CAPABILITY_PROVIDES_TIMETABLED |
            CAPABILITY_MAY_PROVIDE_REAL_TIME);
    }

    protected static $TRANSPORTAPI_APP_ID = '9c1050ef';
    protected static $TRANSPORTAPI_APP_KEY = '6307b57fda47826f87c0e38fcd64e8d4';

    protected function GetTransportAPIInterfaceImplementation($genericLoc = null)
    {
        return new TransportAPI(self::$TRANSPORTAPI_APP_ID, self::$TRANSPORTAPI_APP_KEY);
    }

    public function ArrivalOrDepartureBoard(string $loc, string $filterLoc, bool $isArrivals, DateTime $at, array $searchOptions = null)
    {
        // FIXME Should these be assertions?
        if (!empty($filterLoc))
            throw new OperationNotSupportedException('TransportAPI does not support departures with a filter location');
        if ($isArrivals)
            throw new OperationNotSupportedException('TransportAPI does not support arrivals');

        $TransportAPI = $this->GetTransportAPIInterfaceImplementation($loc);

        $providerLoc = Director::provider_id_for($loc);

        if (in_num_minutes($at) >= -10 && in_num_minutes($at) <= 20) {
            $useNextbuses = !is_array($searchOptions) || array_search(SEARCH_OPTION_AVOID_NEXT_BUSES_API, $searchOptions) === false;   // Limit NextBuses use
            $response = $TransportAPI->BusLiveDepartures($providerLoc, $useNextbuses);
        } else {
            $response = $TransportAPI->BusTimetabledDepartures($providerLoc, $at);
        }
        if ($response !== false) {
            $result = self::normalise_board_response($response, $loc, $searchOptions);

            debugIfTestpilot($response, 'Response');
            debugIfTestpilot($result, 'Normalised response');
        }

        return $result;
    }

    public function ServiceDetails(string $serviceId, string $locOfInterest = null, DateTime $atLocOfInterest = null, array $searchOptions = null)
    {
        throw new OperationNotSupportedException('TransportAPI does not support service details');
    }

    public function ConnectionsBoard(string $loc, string $serviceId = null, DateTime $scheduledArrival = null, int $arrivalBoardIndex = null, array $searchOptions = null)
    {
        throw new OperationNotSupportedException('TransportAPI does not support connections board');
    }

    protected static function normalise_board_response($boardResponse, $genericLoc, $searchOptions = null)
    {
        $genericResponse = new ArrDepBoardAtStop(self::Capabilities());

        $services = array();

        $genericResponse->genericId = $genericLoc;

        $services = self::normalise_service_list($boardResponse, $genericLoc);
        $services = sort_board_entries_by_time($services);

        $genericResponse->services = $services;
        $genericResponse->locationName = $boardResponse['stop_name'] . (isset($boardResponse['indicator']) ? (', ' . $boardResponse['indicator']) : '');

        return $genericResponse;
    }

    protected static function normalise_service_list($response, $genericLoc = '')
    {
        $genericServiceList = [];
        if (isset($response['departures']['all'])) {
            foreach ($response['departures']['all'] as $service) {
                $genericServiceList[] = self::normalise_service($service, $response, $genericLoc);
            }
        }
        return $genericServiceList;
    }

    private static function normalise_service($busDeparture, $result, $genericLoc = '')
    {
        global $GB_TIMEZONE;

        $bus = new ServiceAtStop(self::Capabilities());

        $bus->stopIndexOfInterest = 0;
        $bus->previousLocationsKnown = false;
        $bus->subsequentLocationsKnown = false;
        $bus->connectionConfidence = CONNECTION_CONFIDENCE_NONE;
        $bus->serviceMode = SERVICE_MODE_BUS;
        $bus->isInService = true;
        $bus->routeDescription = $busDeparture['line_name'];
        $bus->operatorName = $busDeparture['operator_name'];
        $bus->operatorCode = $busDeparture['operator'];

        $bus->destination = new ServiceStartOrEndLocation();
        $bus->destination->name = ($busDeparture['line_name'] ?? $busDeparture['line']) . ' to ' . $busDeparture['direction'];

        $atStopLoc = new CommercialStopLocation();

        // Just use stop name if indicator doesn't add anything useful (same as part of name, generic word 'Stop' and others, etc.)
        if ($result['indicator'] === 'Stop' || $result['indicator'] === 'Platform' || $result['indicator'] === '' || strpos($result['stop_name'], $result['indicator']) !== false)
            $atStopLoc->platformOrStop = $result['stop_name'];
        else if (strncmp($result['indicator'], 'Stop', 4) !== 0) { // Some indicators are cryptic (adj, opp) so need the stop name to clarify even if it squashes the display a bit
            $atStopLoc->platformOrStop = $result['stop_name'] . ', ' . $result['indicator'];
        } else { // Use indicator
            $atStopLoc->platformOrStop = $result['indicator'];
        }

        $atStopLoc->departure = new StopMovement();
        if (isset($busDeparture['date'], $busDeparture['aimed_departure_time'])) {
            $atStopLoc->departure->scheduled = DateTime::createFromFormat('Y-m-d/H:i', $busDeparture['date'] . '/' . $busDeparture['aimed_departure_time'], $GB_TIMEZONE);
        }
        if (isset($busDeparture['expected_departure_time'], $busDeparture['expected_departure_date'])) {
            $atStopLoc->departure->realTime = DateTime::createFromFormat('Y-m-d/H:i', $busDeparture['expected_departure_date'] . '/' . $busDeparture['expected_departure_time'], $GB_TIMEZONE);
        }

        if (isset($atStopLoc->departure->scheduled, $atStopLoc->departure->realTime)) {
            $delay = minutes_later($atStopLoc->departure->scheduled, $atStopLoc->departure->realTime);
            if ($delay > 120 || $delay < -30) { // Untrustworthy delay, purge scheduled
                $atStopLoc->departure->state = REAL_TIME_ONLY;
                $atStopLoc->departure->scheduled = null;
            } else if ($delay < 0) {
                $atStopLoc->departure->state = REAL_TIME_EARLY;
            } else if ($delay <= DELAY_THRESHOLD_MINUTES) {
                $atStopLoc->departure->state = REAL_TIME_EXPECTED_ON_TIME;
            } else {
                $atStopLoc->departure->state = REAL_TIME_KNOWN_DELAY;
            }
        } else {
            if (isset($atStopLoc->departure->scheduled))
                $atStopLoc->departure->state = REAL_TIME_SCHEDULED_ONLY;
            else // RT must be set
                $atStopLoc->departure->state = REAL_TIME_ONLY;
        }
        $atStopLoc->isCancelled = false;    // Cancellations not supported by NextBuses and TfL
        $atStopLoc->isExtraStop = false;
        $bus->locations[] = $atStopLoc;

        $bus->source = self::SourceName();

        return $bus;
    }
}
