<?php

interface RealTimeProvider
{
    public function ArrivalOrDepartureBoard(string $loc, string $filterLoc, bool $isArrivals, DateTime $at, array $searchOptions = null);
    public function ConnectionsBoard(string $loc, string $serviceId = null, DateTime $scheduledArrival = null, int $arrivalBoardIndex = null, array $searchOptions = null);
    public function ServiceDetails(string $serviceId, string $locOfInterest = null, DateTime $atLocOfInterest = null, array $searchOptions = null);

    public static function HomeRealm();
    public static function SupportedRealms();
    public static function Capabilities();
    public static function GenericTransportMode(string $providerMode);
    public static function SourceName();
}

const CAPABILITY_PROVIDES_TIMETABLED = 1;
const CAPABILITY_MAY_PROVIDE_REAL_TIME = 2;
const CAPABILITY_PROVIDES_SERVICE_JOURNEY_DETAIL = 4;
const CAPABILITY_SUPPORTS_ARRIVALS_AND_DEPARTURES = 8;
const CAPABILITY_SUPPORTS_CONNECTIONS = 16;
const CAPABILITY_SUPPORTS_FILTER_BY_SECOND_LOCATION = 32;
const CAPABILITY_SHOWS_STOP_OR_PLATFORM_ON_SIMPLE_BOARD = 64;
const CAPABILITY_SUPPORTS_AUTO_REFRESH = 128;
const CAPABILITY_SUPPORTS_JOURNEY_PLANNING = 256;

const ALERT_MESSAGE_CATEGORY_NONE = 'none';
const ALERT_MESSAGE_CATEGORY_STOP = 'stationOrStop';
const ALERT_MESSAGE_CATEGORY_SERVICE = 'service';
const ALERT_MESSAGE_CATEGORY_OTHER = 'other';

const ALERT_MESSAGE_SEVERITY_NONE = 0;
const ALERT_MESSAGE_SEVERITY_INFO = 1;
const ALERT_MESSAGE_SEVERITY_LOW = 2;
const ALERT_MESSAGE_SEVERITY_MEDIUM = 3;
const ALERT_MESSAGE_SEVERITY_HIGH = 4;

const SERVICE_MODE_TRAIN = 'train';
const SERVICE_MODE_BUS = 'bus';
const SERVICE_MODE_TRAM = 'tram';
const SERVICE_MODE_METRO_LIGHT_RAIL = 'metro';
const SERVICE_MODE_FERRY = 'ferry';
const SERVICE_MODE_BUS_REPLACING_TRAIN = 'replacement_bus';
const SERVICE_MODE_DOCKLANDS_LIGHT_RAILWAY = 'dlr';
const SERVICE_MODE_GLASGOW_SUBWAY = 'subway';
const SERVICE_MODE_T_AND_W_METRO = 'twmetro';
const SERVICE_MODE_UNDERGROUND = 'tube';

const PLATFORM_OR_STOP_NONE = 'none';
const PLATFORM_OR_STOP_SECRET = 'secret';
const PLATFORM_OR_STOP_SCHEDULED = 'scheduled';
const PLATFORM_OR_STOP_REAL_TIME = 'realTime';
const PLATFORM_OR_STOP_ALTERED = 'alteration';
const PLATFORM_OR_STOP_REPORTED = 'reported';

const STOP_TYPE_INTERMEDIATE = 'normal';
const STOP_TYPE_ORIGIN = 'origin';
const STOP_TYPE_TERMINUS = 'terminus';
const STOP_TYPE_PICK_UP_ONLY = 'pickUp';
const STOP_TYPE_SET_DOWN_ONLY = 'setDown';
const STOP_TYPE_RAILWAY_REQUEST = 'request';
const STOP_TYPE_UNADVERTISED = 'unadvertised';

const REAL_TIME_SCHEDULED_ONLY = 'noInfo';
const REAL_TIME_EXPECTED_ON_TIME = 'onTime';
const REAL_TIME_EARLY = 'early';
const REAL_TIME_KNOWN_DELAY = 'late';
const REAL_TIME_UNKNOWN_DELAY = 'delayed';
const REAL_TIME_ONLY = 'rtOnly';
const REAL_TIME_EVENT_CONFIRMED = 'confirmed';
// TODO Reinstate once working, other RT systems may do cancellations this way and need it: const REAL_TIME_CANCELLED = 'cancelled';

const ASSOCIATION_TYPE_SPLIT = 'split';
const ASSOCIATION_TYPE_JOIN = 'join';
const ASSOCIATION_TYPE_CONNECTION = 'continuation';
const ASSOCIATION_TYPE_DIAGRAM = 'diagram';

const CONNECTION_CONFIDENCE_UNKNOWN = 'unknown';
const CONNECTION_CONFIDENCE_NONE = 'notConnection';
const CONNECTION_CONFIDENCE_LIKELY = 'likely';
const CONNECTION_CONFIDENCE_IN_DOUBT = 'inDoubt';
const CONNECTION_CONFIDENCE_IMPOSSIBLE = 'impossible';
const CONNECTION_CONFIDENCE_CANCELLED = 'cancelled';
const CONNECTION_CONFIDENCE_GUARANTEED = 'guaranteed';

const DELAY_THRESHOLD_MINUTES = 2;
const DELAY_THRESHOLD_SECONDS = DELAY_THRESHOLD_MINUTES * 60;

const SEARCH_OPTION_SHOW_ECS = 'ECS';
const SEARCH_OPTION_AVOID_NEXT_BUSES_API = 'AvoidNextBuses';

# GCP uses PHP < 7.4, which can't handle property types

class ReportableEntity
{
    public /*InformationProviderCapabilities*/ $provider;

    function __construct(InformationProviderCapabilities $provider)
    {
        $this->provider = $provider;
    }
}

class AlertMessage
{
    public /*string*/ $text;
    public /*string */ $category;   /* ALERT_MESSAGE_CATEGORY_* */
    public /*int*/ $severity;       /* ALERT_MESSAGE_SEVERITY_* */
    public /*string*/ $url;
    public /*bool*/ $resolved;

    function __construct(string $text, string $category, int $severity, string $url = null, bool $resolved = false)
    {
        $this->text = $text;
        $this->category = $category;
        $this->severity = $severity;
        $this->url = $url;
        $this->resolved = $resolved;
    }
}

class PlannedService extends ReportableEntity
{
    public /*InformationProviderCapabilities*/ $provider;
    public /*string*/ $serviceMode; /* SERVICE_MODE_* */
    public /*bool*/ $isInService;
    public /*bool*/ $isExpress;
    public /*bool*/ $isSleeper;
    public /*string*/ $length;
    public /*string*/ $genericId;
    public /*array of string*/ $providerId;
    public /*string*/ $routeDescription;
    public /*string*/ $operatorName;
    public /*string*/ $operatorCode;
    public /*string*/ $operatorBrand;
    public /*array of ServiceStartOrEndLocation*/ $origin;
    public /*array of ServiceStartOrEndLocation*/ $destination;
    public /*array of StopLocation*/ $locations;
    public /*bool*/ $isCircular;
    public /*bool*/ $isExtraService;
    public /*int*/ $minCheckIn;

    function __construct(InformationProviderCapabilities $provider)
    {
        parent::__construct($provider);
        $this->isInService = false;
        $this->isExpress = false;
        $this->isSleeper = false;
        $this->isCircular = false;
        $this->isExtraService = false;
    }

    public function HasLocations()
    {
        return isset($locations);
    }

    public function VisitsLocationAfterIndex(string $genericLoc, int $afterIndex, bool $considerRT = true)
    {
        if (!isset($this->locations)) return false;

        foreach (array_slice($this->locations, $afterIndex) as $laterLoc) {
            if ($laterLoc instanceof CommercialStopLocation && $genericLoc === $laterLoc->genericId) {
                if (!$considerRT || !$laterLoc->isCancelled) {
                    return true;
                }
            }
        }

        return false;
    }
}

class Vehicle
{
    public /*string*/ $identity;
    public /*string*/ $comfortName;
    public /*string*/ $comfortShortName;
    public /*bool*/ $hasToilet;
    public /*bool*/ $toiletKnownInService;
    public /*bool*/ $toiletKnownOutOfService;
    public /*bool*/ $isUniversalToilet;
    public /*array of string*/ $facilities;
    public /*int*/ $realTimeLoading;
}

class Formation
{
    public /*array of Vehicle*/ $vehicles;
    public /*int*/ $averageLoading;
    public /*string*/ $note;
}

class Service extends PlannedService
{
    public /*Location*/ $modifiedOrigin;
    public /*Location*/ $modifiedDestination;
    public /*float*/ $latitude;
    public /*float*/ $longitude;
    public /*string*/ $currentLocationDescription;
    public /*int*/ $latestStopIndex;
    public /*Problem*/ $cancellationReason;
    public /*Problem*/ $delayReason;
    public /*Date*/ $scheduledDepartureDateFromOrigin;
    public /*array of AlertMessage*/ $messages;
    public /*bool*/ $reportedFull;
    public /*Formation*/ $formation;
    public /*string*/ $source;

    function __construct(InformationProviderCapabilities $provider)
    {
        parent::__construct($provider);
    }
}

class AssociatedService
{
    public /*ServiceStartOrEndLocation*/ $otherServiceDistalLoc;
    public /*array of string*/ $otherServiceId;
    public /*string*/ $otherServiceGenericId;
    public /*int*/ $associationType;    /* ASSOCIATION_TYPE_* */
}

class Location
{
    public /*string*/ $name;
    public /*string*/ $genericId;
    public /*array of string*/ $providerId;
}

class ServiceStartOrEndLocation extends Location
{
    public /*string*/ $via;
}

abstract class StopLocation extends Location
{
    public /*int*/ $lateness;
    public /*array of AssociatedService*/ $associations;
    public /*bool*/ $isCancelled;

    public function __construct()
    {
        $this->isCancelled = false;
    }

    public abstract function IsCommercialStop();
    public abstract function HeadlineTime();
}

class OperationalStopLocation extends StopLocation
{
    public /*StopMovement*/ $arrival;
    public /*StopMovement*/ $departure;
    public /*array of StopMovement*/ $subsequentDepartures;
    public /*string*/ $platformOrStop;
    public /*string*/ $platformOrStopState;
    public /*string*/ $platformOrStopAtOtherLocId;
    public /*bool*/ $isExtraStop;

    function __construct()
    {
        parent::__construct();
        $this->platformOrStopState = PLATFORM_OR_STOP_NONE;
        $this->isExtraStop = false;
    }

    public function IsCommercialStop()
    {
        return false;
    }

    public function HeadlineTime()
    {
        return (isset($this->departure) ? $this->departure->scheduled : $this->arrival->scheduled) ?? false;
    }
}

class CommercialStopLocation extends OperationalStopLocation
{
    public /*string*/ $platformOrStopSpecial;
    public /*Formation*/ $formation;

    function __construct()
    {
        parent::__construct();
        $this->platformOrStopSpecial = STOP_TYPE_INTERMEDIATE;
    }

    public function IsCommercialStop()
    {
        return true;
    }
}

class PassLocation extends StopLocation
{
    public /*StopMovement*/ $pass;

    public function IsCommercialStop()
    {
        return false;
    }

    public function HeadlineTime()
    {
        return $this->pass->scheduled ?? false;
    }
}

class StopMovement
{
    public /*DateTime*/ $scheduled;
    public /*DateTime*/ $realTime;
    public /*string*/ $state; /* REAL_TIME_* */

    public function IsConfirmed()
    {
        return $this->state === REAL_TIME_EVENT_CONFIRMED;
    }

    public function IsEstimated()
    {
        return $this->state === REAL_TIME_EXPECTED_ON_TIME || $this->state === REAL_TIME_KNOWN_DELAY || $this->state === REAL_TIME_UNKNOWN_DELAY;
    }

    public function HasRT()
    {
        return $this->state !== REAL_TIME_SCHEDULED_ONLY;
    }

    function __construct()
    {
        $this->state = REAL_TIME_SCHEDULED_ONLY;
    }
}

class Problem
{
    public /*string*/ $supplierSpecificCode;
    public /*string*/ $reason;
    public /*string*/ $problemLocation;
}

class ServiceAtStop extends Service
{
    public /*int*/ $stopIndexOfInterest;
    public /*bool*/ $previousLocationsKnown;
    public /*bool*/ $subsequentLocationsKnown;
    public /*string*/ $connectionConfidence; /* CONNECTION_CONFIDENCE_* */

    function __construct(InformationProviderCapabilities $provider)
    {
        parent::__construct($provider);
        $this->previousLocationsKnown = false;
        $this->subsequentLocationsKnown = false;
        $this->connectionConfidence = CONNECTION_CONFIDENCE_NONE;
    }

    public function AtThisLocation()
    {
        return isset($this->stopIndexOfInterest) ? $this->locations[$this->stopIndexOfInterest] : false;
    }

    public function MovementAtThisLocation(bool $isDeparture)
    {
        $stop = $this->AtThisLocation();
        if (!isset($stop)) return false;
        return $isDeparture ? (isset($stop->arrival) ? $stop->arrival : $stop->departure) : (isset($stop->departure) ? $stop->departure : $stop->arrival);
    }

    public function PreviousLocations()
    {
        return isset($this->stopIndexOfInterest) ? array_slice($this->locations, 0, $this->stopIndexOfInterest) : [];
    }

    public function SubsequentLocations()
    {
        return isset($this->stopIndexOfInterest) ? array_slice($this->locations, $this->stopIndexOfInterest + 1) : $this->locations;
    }
}

class Leg
{
    public /*Service*/ $service;
    public /*int*/ $board;
    public /*int*/ $alight;
}

class Journey
{
    public /*array of Leg*/ $legs;
}
    
class ArrDepBoardAtStop
{
    public /*InformationProviderCapabilities*/ $provider;
    public /*string*/ $genericId;
    public /*string*/ $locationName;
    public /*string*/ $filterLocationName;
    public /*array of AlertMessage*/ $messages;
    public /*array of ServiceAtStop*/ $services;

    public function __construct(InformationProviderCapabilities $provider)
    {
        $this->provider = $provider;
    }
}

class ConnectionsBoard
{
    public /*InformationProviderCapabilities*/ $provider;
    public /*string*/ $locationName;
    public /*ServiceAtStop*/ $arrival;
    public /*ArrDepBoardAtStop*/ $departures;

    public function __construct(InformationProviderCapabilities $provider, string $locationName, ServiceAtStop $arrival = null, ArrDepBoardAtStop $departures = null)
    {
        $this->provider = $provider;
        $this->locationName = $locationName;
        $this->arrival = $arrival;
        $this->departures = $departures;
    }
}

class InformationProviderCapabilities
{
    public /*int*/ $capabilities;

    function __construct(int $capabilities)
    {
        $this->capabilities = $capabilities;
    }

    function providesTimetabled()
    {
        return $this->capabilities & CAPABILITY_PROVIDES_TIMETABLED;
    }
    function mayProvideRealTime()
    {
        return $this->capabilities & CAPABILITY_MAY_PROVIDE_REAL_TIME;
    }
    function providesServiceJourneyDetail()
    {
        return $this->capabilities & CAPABILITY_PROVIDES_SERVICE_JOURNEY_DETAIL;
    }
    function supportsArrivalsAndDepartures()
    {
        return $this->capabilities & CAPABILITY_SUPPORTS_ARRIVALS_AND_DEPARTURES;
    }
    function supportsConnections()
    {
        return $this->capabilities & CAPABILITY_SUPPORTS_CONNECTIONS;
    }
    function supportsFilterBySecondLoc()
    {
        return $this->capabilities & CAPABILITY_SUPPORTS_FILTER_BY_SECOND_LOCATION;
    }
    function showsStopOrPlatformOnSimpleBoard()
    {
        return $this->capabilities & CAPABILITY_SHOWS_STOP_OR_PLATFORM_ON_SIMPLE_BOARD;
    }
    function supportsAutoRefresh()
    {
        return $this->capabilities & CAPABILITY_SUPPORTS_AUTO_REFRESH;
    }
    function supportsJourneyPlanning()
    {
        return $this->capabilities & CAPABILITY_SUPPORTS_JOURNEY_PLANNING;
    }
}

function ensure_is_array($possibleArray = null)
{
    if (!isset($possibleArray)) {
        $possibleArray = [];
    } else {
        if (!is_array($possibleArray)) {
            $possibleArray = array($possibleArray);
        }
    }
    return $possibleArray;
}

class OperationNotSupportedException extends Exception
{
}
