<?php

require_once __DIR__ . '/../controller/time.php';
require_once __DIR__ . '/../controller/model.php';

// FIXME dep, arr and connections are now very similar
function get_html_for_departure_board_details(ServiceAtStop $info, string $filterLoc, $searchOptions = null)
{
    $html = '';

    if ($info->isInService) {
        // Prefer departure
        $mov = isset($info->AtThisLocation()->departure) ? $info->AtThisLocation()->departure : $info->AtThisLocation()->arrival;
        $timeNote = '';
        if (!isset($info->AtThisLocation()->departure)) {
            $timeNote = ' (arr.)';
        }
        switch ($info->AtThisLocation()->platformOrStopSpecial) {
            case STOP_TYPE_SET_DOWN_ONLY:
                $passengerNote = '<span class="ap-train-use-note">Stops to set-down only</span>';
                break;
            case STOP_TYPE_PICK_UP_ONLY:
                $passengerNote = '<span class="ap-train-use-note">Stops to pick-up only</span>';
                break;
            case STOP_TYPE_RAILWAY_REQUEST:
                $passengerNote = '<span class="ap-train-use-note">Stops on request</span>';
                break;
            case STOP_TYPE_UNADVERTISED:
                $passengerNote = '<span class="ap-train-use-note">Unadvertised stop</span>';
                break;
        }

        $departureStatus = status_colour($info->AtThisLocation());
        $filterStatus = $departureStatus;
        if (isset($filterLoc)) {
            foreach ($info->SubsequentLocations() as $laterStop) {
                if ($laterStop->genericId === $filterLoc) {
                    $filterStatus = status_colour($laterStop, true);
                    break;
                }
            }
        }
        $html .= '<tr class="ap-board-service"><td class="ap-board-time" style="background-image: linear-gradient(to right, ' . $departureStatus . ', ' . $filterStatus . ')">' . (isset($mov->scheduled) ? $mov->scheduled->format(TIME_FORMAT) : '') . $timeNote . '</td>';
        if ($info->provider->showsStopOrPlatformOnSimpleBoard()) {
            $html .= '<td class="ap-board-platform">';
            if ($info->serviceMode === SERVICE_MODE_BUS_REPLACING_TRAIN || $info->serviceMode === SERVICE_MODE_BUS) {
                $html .= '<i class="material-icons">airport_shuttle</i>';
            } elseif (isset($info->AtThisLocation()->platformOrStop) && !$info->AtThisLocation()->isCancelled) {
                $html .= '<span class="ap-platform-' . $info->AtThisLocation()->platformOrStopState . '">' . $info->AtThisLocation()->platformOrStop . '</span>';
            }
            $html .= '</td>';
        }
        $html .= '<td class="ap-board-orig-dest">';
        if ($info->provider->providesServiceJourneyDetail())
            $html .= '<a href = "/service/?serviceId=' . urlencode($info->genericId) . '&atLoc=' . urlencode($info->AtThisLocation()->genericId) . '&atTime=' . $mov->scheduled->format(DATE_AND_TIME_FORMAT_NO_TZ) . search_options($searchOptions) . '">';
        $html .= get_html_for_orig_or_dest_info($info, true, true);
        if ($info->provider->providesServiceJourneyDetail())
            $html .= '</a>';
        if ($info->AtThisLocation()->isCancelled && isset($info->cancellationReason)) {
            $html .= '<br>' . htmlspecialchars($info->operatorName) . ' ' . ($info->serviceMode === SERVICE_MODE_BUS_REPLACING_TRAIN ? 'rail replacement bus' : htmlspecialchars($info->serviceMode)) . ' from ';
            $html .= get_html_for_simple_origs_or_dests($info, false);
            $html .= '<br><span class="ap-cancel-info"><i class="material-icons">error</i> ' . htmlspecialchars($info->cancellationReason->reason) . '</span>';
        }
        if (isset($info->modifiedOrigin)) {
            $html .= '<br><span class="ap-cancel-info"><i class="material-icons">info</i> Will now start from ';
            foreach ($info->modifiedOrigin as $index => $orig) :
                $html .= htmlspecialchars($orig->name);
                if (isset($orig->via))
                    $html .= ' ' . htmlspecialchars($orig->via);
                if ($index < count($info->modifiedOrigin) - 1)
                    $html .= ' and ';
            endforeach;
            $html .= '</span>';
        }
        if (isset($info->modifiedDestination)) {
            $html .= '<br><span class="ap-cancel-info"><i class="material-icons">info</i> Will now terminate at ';
            foreach ($info->modifiedDestination as $index => $dest) :
                $html .= htmlspecialchars($dest->name);
                if (isset($dest->via))
                    $html .= ' ' . htmlspecialchars($dest->via);
                if ($index < count($info->modifiedDestination) - 1)
                    $html .= ' and ';
            endforeach;
            $html .= '</span>';
        }
        if (isset($passengerNote)) {
            $html .= '<br>' . $passengerNote;
        }
        if (isset($info->minCheckIn, $info->AtThisLocation()->departure)) {
            $html .= '<br> Please note the minimum check-in time of ' . $info->minCheckIn . ' minutes before scheduled departure';
        }

        $html .= '</td><td class="ap-board-expected">';

        $html .= board_expected_time(
            $info->AtThisLocation(),
            $mov,
            isset($info->AtThisLocation()->arrival) && $info->AtThisLocation()->arrival->IsConfirmed(),
            isset($info->AtThisLocation()->departure) && $info->AtThisLocation()->departure->IsConfirmed()
        );

        $html .= '</td></tr>';
        if (!$info->AtThisLocation()->isCancelled && $info->provider->providesServiceJourneyDetail()) {
            $html .= '<tr><td colspan="4">';
            $html .= get_html_for_board_calling_points($info, $info->SubsequentLocations(), true, $filterLoc, $searchOptions);
            $html .= '</td></tr>';
        } else if (isset($info->currentLocationDescription)) {
            $html .= '<tr><td colspan="4"><span class="ap-current-location"><i class="material-icons">location_on</i>&nbsp;' . $info->currentLocationDescription . '</span></td></tr>';
        }

        $html .= "\n\n";
    } else {
        if ($info->stopIndexOfInterest === count($info->locations) - 1)
            return; // Don't show terminating ECS
        // Prefer departure
        $mov = isset($info->AtThisLocation()->departure) ? $info->AtThisLocation()->departure : $info->AtThisLocation()->arrival;
        $timeNote = '';
        if (!isset($info->AtThisLocation()->departure)) {
            $timeNote = ' (arr.)';
        } // FIXME Possbily always a departure

        $html .= '<tr class="ap-board-service ap-board-service-ecs"><td class="ap-board-time" style="background-color:pink">' . $mov->scheduled->format(TIME_FORMAT) . $timeNote . '</td><td class="ap-board-platform">';
        if ($info->serviceMode === SERVICE_MODE_BUS_REPLACING_TRAIN) {
            $html .= '<i class="material-icons">airport_shuttle</i>';
        } elseif (isset($info->AtThisLocation()->platformOrStop) && !$info->AtThisLocation()->isCancelled) {
            $html .= '<span class="ap-platform-' . $info->AtThisLocation()->platformOrStopState . '">' . $info->AtThisLocation()->platformOrStop . '</span>';
        }
        $html .= '</td><td class="ap-board-orig-dest">';
        $html .= get_html_for_orig_or_dest_info($info, true, true);
        $html .= '</td><td class="ap-board-expected">';
        $html .= board_expected_time(
            $info->AtThisLocation(),
            $mov,
            isset($info->AtThisLocation()->arrival) && $info->AtThisLocation()->arrival->IsConfirmed(),
            isset($info->AtThisLocation()->departure) && $info->AtThisLocation()->departure->IsConfirmed()
        );
        $html .= '</td></tr>';
        $html .= "\n\n";
    }

    return $html;
}

function get_html_for_arrival_board_details(ServiceAtStop $info, string $filterLoc, $searchOptions = null)
{
    $html = '';

    if ($info->isInService) {
        if ($info->stopIndexOfInterest === 0) {
            return; // At origin, don't show this as an arrival
        }
        // Prefer arrival
        $mov = isset($info->AtThisLocation()->arrival) ? $info->AtThisLocation()->arrival : $info->AtThisLocation()->departure;
        $timeNote = '';
        if (!isset($info->AtThisLocation()->arrival)) {
            $timeNote = ' (dep.)';
        }
        switch ($info->AtThisLocation()->platformOrStopSpecial) {
            case STOP_TYPE_SET_DOWN_ONLY:
                $passengerNote = '<span class="ap-train-use-note">Stops toStops to set-down only</span>';
                break;
            case STOP_TYPE_PICK_UP_ONLY:
                $passengerNote = '<span class="ap-train-use-note">Stops to pick-up only</span>';
                break;
            case STOP_TYPE_RAILWAY_REQUEST:
                $passengerNote = '<span class="ap-train-use-note">Stops on request</span>';
                break;
            case STOP_TYPE_UNADVERTISED:
                $passengerNote = '<span class="ap-train-use-note">Unadvertised stop</span>';
                break;
        }

        $arrivalStatus = status_colour($info->AtThisLocation());
        $filterStatus = $arrivalStatus;
        if (isset($filterLoc)) {
            foreach (array_reverse($info->PreviousLocations()) as $earlierStop) {
                if ($earlierStop->genericId === $filterLoc) {
                    $filterStatus = status_colour($earlierStop, true);
                    break;
                }
            }
        }
        $html .= '<tr class="ap-board-service"><td class="ap-board-time" style="background-image: linear-gradient(to right, ' . $filterStatus . ', ' . $arrivalStatus . ')">' . $mov->scheduled->format(TIME_FORMAT) . $timeNote . '</td>';
        if ($info->provider->showsStopOrPlatformOnSimpleBoard()) {
            $html .= '<td class="ap-board-platform">';
            if ($info->serviceMode === SERVICE_MODE_BUS_REPLACING_TRAIN || $info->serviceMode === SERVICE_MODE_BUS) {
                $html .= '<i class="material-icons">airport_shuttle</i>';
            } elseif (isset($info->AtThisLocation()->platformOrStop) && !$info->AtThisLocation()->isCancelled) {
                $html .= '<span class="ap-platform-' . $info->AtThisLocation()->platformOrStopState . '">' . $info->AtThisLocation()->platformOrStop . '</span>';
            }
            $html .= '</td>';
        }
        $html .= '<td class="ap-board-orig-dest">';
        if ($info->provider->providesServiceJourneyDetail())
            $html .= '<a href = "/service/?serviceId=' . urlencode($info->genericId) . '&atLoc=' . urlencode($info->AtThisLocation()->genericId) . '&atTime=' . $mov->scheduled->format(DATE_AND_TIME_FORMAT_NO_TZ) . search_options($searchOptions) . '">';
        $html .= get_html_for_orig_or_dest_info($info, false, true);
        if ($info->provider->providesServiceJourneyDetail())
            $html .= '</a>';
        if ($info->AtThisLocation()->isCancelled && isset($info->cancellationReason)) {
            $html .= '<br>' . htmlspecialchars($info->operatorName) . ' ' . ($info->serviceMode === SERVICE_MODE_BUS_REPLACING_TRAIN ? 'rail replacement bus' : htmlspecialchars($info->serviceMode)) . ' to ';
            $html .= get_html_for_simple_origs_or_dests($info, true);
            $html .= '<br><span class="ap-cancel-info"><i class="material-icons">error</i> ' . htmlspecialchars($info->cancellationReason->reason) . '</span>';
        }
        if (isset($info->modifiedOrigin)) {
            $html .= '<br><span class="ap-cancel-info"><i class="material-icons">info</i> Will now start from ';
            foreach ($info->modifiedOrigin as $index => $orig) :
                $html .= htmlspecialchars($orig->name);
                if (isset($orig->via))
                    $html .= ' ' . htmlspecialchars($orig->via);
                if ($index < count($info->modifiedOrigin) - 1)
                    $html .= ' and ';
            endforeach;
            $html .= '</span>';
        }
        if (isset($info->modifiedDestination)) {
            $html .= '<br><span class="ap-cancel-info"><i class="material-icons">info</i> Will now terminate at ';
            foreach ($info->modifiedDestination as $index => $dest) :
                $html .= htmlspecialchars($dest->name);
                if (isset($dest->via))
                    $html .= ' ' . htmlspecialchars($dest->via);
                if ($index < count($info->modifiedDestination) - 1)
                    $html .= ' and ';
            endforeach;
            $html .= '</span>';
        }
        if (isset($passengerNote)) {
            $html .= '<br>' . $passengerNote;
        }

        $html .= '</td><td class="ap-board-expected">';
        $html .= board_expected_time(
            $info->AtThisLocation(),
            $mov,
            isset($info->AtThisLocation()->arrival) && $info->AtThisLocation()->arrival->IsConfirmed(),
            isset($info->AtThisLocation()->departure) && $info->AtThisLocation()->departure->IsConfirmed()
        );
        $html .= '</td></tr>';
        if (!$info->AtThisLocation()->isCancelled && $info->provider->providesServiceJourneyDetail()) {
            $html .= '<tr><td colspan="4">';
            $html .= get_html_for_board_calling_points($info, $info->PreviousLocations(), false, $filterLoc, $searchOptions);
            $html .= '</td></tr>';
        }
        $html .= "\n\n";
    }

    return $html;
}

function get_html_for_connections_board_arrival(ServiceAtStop $info)
{
    $html = '';

    $html .= '<tr class="ap-board-arrival"><td class="ap-board-time" style="background-color:' . status_colour($info->AtThisLocation(), true) . '">' . $info->AtThisLocation()->arrival->scheduled->format(TIME_FORMAT) . '</td><td class="ap-board-platform">';
    /*if ($info->serviceMode === SERVICE_MODE_BUS_REPLACING_TRAIN) {
        $html .= '<i class="material-icons">airport_shuttle</i>';
    } else*/
    if (isset($info->AtThisLocation()->platformOrStop) && !$info->AtThisLocation()->isCancelled) {
        $html .= $info->AtThisLocation()->platformOrStop;
    }
    $html .= '</td><td class="ap-board-expected">';

    $html .= board_expected_time_as_hh_mm(
        $info->AtThisLocation(),
        $info->AtThisLocation()->arrival,
        isset($info->AtThisLocation()->arrival) && $info->AtThisLocation()->arrival->IsConfirmed(),
        isset($info->AtThisLocation()->departure) && $info->AtThisLocation()->departure->IsConfirmed()
    );

    $html .= '</td></tr>';

    if (isset($info->delayReason) && $info->MovementAtThisLocation(false)->state !== REAL_TIME_EXPECTED_ON_TIME) {
        $html .= ' <tr><td colspan="3"><span class="ap-delay-info"><i class="material-icons">warning</i> ' . htmlspecialchars($info->delayReason->reason) . '</span></td></tr>';
    }

    $html .= "\n\n";

    return $html;
}

function get_html_for_connections_board_details(ServiceAtStop $connection, bool $simplified = false, bool $hasArrival = true, $searchOptions = null)
{
    $html = '';

    if ($connection->isInService && isset($connection->AtThisLocation()->departure)) {
        // Always departure
        $mov = $connection->AtThisLocation()->departure;

        if ($connection->AtThisLocation()->platformOrStopSpecial === STOP_TYPE_RAILWAY_REQUEST) {
            $passengerNote = '<span class="ap-train-use-note">Stops on request</span>';
        }

        $html .= '<tr class="' . ($simplified ? 'ap-inline-connection' : 'ap-board-service') . '">';
        if ($hasArrival) {
            $html .= '<td class="ap-board-connection-status center-align">';

            switch ($connection->connectionConfidence) {
                case CONNECTION_CONFIDENCE_LIKELY:
                    $html .= '<span class="status-dot" style="background-color:green"></span>';
                    //$confidenceNote = 'This connection is likely to be reached.';
                    break;
                case CONNECTION_CONFIDENCE_IN_DOUBT:
                    $html .= '<span class="status-dot" style="background-color:yellow"></span>';
                    //$confidenceNote = 'This connection may not be possible.';
                    break;
                case CONNECTION_CONFIDENCE_IMPOSSIBLE:
                    $html .= '<span class="status-dot" style="background-color:red"></span>';
                    //$confidenceNote = 'With current information, this connection is not possible.';
                    break;
                case CONNECTION_CONFIDENCE_CANCELLED:
                    $html .= '<span class="status-dot" style="background-color:red"></span>';
                    //$confidenceNote = 'The connecting service has been cancelled.';
                    break;
                case CONNECTION_CONFIDENCE_NONE:
                    //$confidenceNote = '';
                    break;
            }

            $html .= '</td>';
        }
        $html .= '<td class="ap-board-time" style="background-color:' . status_colour($connection->AtThisLocation()) . '">' . (isset($mov->scheduled) ? $mov->scheduled->format(TIME_FORMAT) : '') . '</td><td class="ap-board-platform">';
        if ($connection->serviceMode === SERVICE_MODE_BUS_REPLACING_TRAIN) {
            $html .= '<i class="material-icons">airport_shuttle</i>';
        } elseif (isset($connection->AtThisLocation()->platformOrStop)) {
            if (isset($connection->AtThisLocation()->platformOrStopAtOtherLocId)) {
                $html .= '<span class="ap-platform-' . $connection->AtThisLocation()->platformOrStopState . '"><a href="/board/?loc=' . $connection->AtThisLocation()->platformOrStopAtOtherLocId . search_options($searchOptions) . '">' . (strlen($connection->AtThisLocation()->platformOrStop) <= 3 ? 'Platform ' : '') . $connection->AtThisLocation()->platformOrStop . '</a></span>';
            } else {
                $html .= '<span class="ap-platform-' . $connection->AtThisLocation()->platformOrStopState . '">' . (strlen($connection->AtThisLocation()->platformOrStop) <= 3 ? 'Platform ' : '') . $connection->AtThisLocation()->platformOrStop . '</span>';
            }
        }
        $html .= '</td><td class="ap-board-orig-dest">';
        if ($connection->provider->providesServiceJourneyDetail())
            $html .= '<a href = "/service/?serviceId=' . urlencode($connection->genericId) . '&atLoc=' . urlencode($connection->AtThisLocation()->genericId) . '&atTime=' . $connection->AtThisLocation()->departure->scheduled->format(DATE_AND_TIME_FORMAT_NO_TZ) . search_options($searchOptions) . '">';
        $html .= get_html_for_orig_or_dest_info($connection, true, true);
        if ($connection->provider->providesServiceJourneyDetail())
            $html .= '</a>';
        if ($connection->AtThisLocation()->isCancelled && isset($connection->cancellationReason)) {
            $html .= '<br><span class="ap-cancel-info"><i class="material-icons">error</i> ' . htmlspecialchars($connection->cancellationReason->reason) . '</span>';
        }
        if (isset($connection->modifiedDestination)) {
            $html .= '<br><span class="ap-cancel-info"><i class="material-icons">info</i> Will now terminate at ';
            foreach ($connection->modifiedDestination as $index => $dest) :
                $html .= htmlspecialchars($dest->name);
                if (isset($dest->via))
                    $html .= ' ' . htmlspecialchars($dest->via);
                if ($index < count($connection->modifiedDestination) - 1)
                    $html .= ' and ';
            endforeach;
            $html .= '</span>';
        }
        if (isset($passengerNote)) {
            $html .= '<br>' . $passengerNote;
        }

        $html .= '</td><td class="ap-board-expected">';

        $html .= board_expected_time(
            $connection->AtThisLocation(),
            $mov,
            isset($connection->AtThisLocation()->arrival) && $connection->AtThisLocation()->arrival->IsConfirmed(),
            isset($connection->AtThisLocation()->departure) && $connection->AtThisLocation()->departure->IsConfirmed()
        );

        if (isset($connection->AtThisLocation()->subsequentDepartures)) {
            $departuresHtml = [];
            foreach ($connection->AtThisLocation()->subsequentDepartures as $subsequentDeparture) {
                $departuresHtml[] = board_expected_time(
                    $connection->AtThisLocation(),
                    $subsequentDeparture
                );
            }

            $html .= ', then ' . concatenateWithPunctuation($departuresHtml);
        }

        $html .= '</td></tr>';

        /* TODO: May replace later if a reasonable tooltip/modal can work on a mobile
        if (!$simplified && ($hasArrival || !$connection->AtThisLocation()->isCancelled)) {
            $html .= '<tr><td colspan="5">';
            if ($hasArrival) $html .= '<span style="font-weight: bold">' . $confidenceNote . '</span>  ';
            if (!$connection->AtThisLocation()->isCancelled && $connection->provider->providesServiceJourneyDetail()) {
                get_html_for_board_calling_points($connection, $connection->SubsequentLocations(), true);
            }
            $html .= '</td></tr>';
        }*/

        $html .= "\n\n";
    }

    return $html;
}

function board_expected_time(OperationalStopLocation $loc, StopMovement $stop, bool $hasArrived = false, bool $hasDeparted = false)
{
    return board_expected_time_impl(true, $loc, $stop, $hasArrived, $hasDeparted);
}

function board_expected_time_as_hh_mm(OperationalStopLocation $loc, StopMovement $stop, bool $hasArrived = false, bool $hasDeparted = false)
{
    return board_expected_time_impl(false, $loc, $stop, $hasArrived, $hasDeparted);
}

function board_expected_time_impl(bool $showAsMinutesUnderHalfHour, OperationalStopLocation $loc, StopMovement $stop, bool $hasArrived = false, bool $hasDeparted = false)
{
    //    $html .= '<span class="ap-realtime">';

    $html = '';

    if ($hasDeparted) {
        $html .= 'Left at ' . $stop->realTime->format(TIME_FORMAT_INC_SECONDS);
    } else {
        if ($loc->isCancelled) {
            $html .= 'Cancelled';
        } else if ($stop->state === REAL_TIME_UNKNOWN_DELAY) {
            $html .= 'Delayed';
        } else if ($stop->state === REAL_TIME_SCHEDULED_ONLY) {
            $html .= $stop->scheduled->format(TIME_FORMAT) . '&nbsp;<i class="tiny material-icons">timer_off</i>';
        } else {
            $minsAway = in_num_minutes($stop->realTime);

            if ($hasArrived) {
                $html .= 'Arrived';
            } else if ($showAsMinutesUnderHalfHour) {
                if ($minsAway < -1)
                    $html .= $minsAway . ' mins ago';
                elseif ($minsAway < 1)
                    $html .= 'Due';
                elseif ($minsAway === 1)
                    $html .= $minsAway . ' min';
                elseif ($minsAway <= 30)
                    $html .= $minsAway . ' mins';
                else
                    $html .= $stop->realTime->format(TIME_FORMAT);
            } else {
                $html .= $stop->realTime->format(TIME_FORMAT);
            }
            if (isset($stop->scheduled)) {
                $delay = minutes_later($stop->scheduled, $stop->realTime);
                if ($delay >= 5)
                    $html .= '&nbsp;<span style="color:red;"><span class="ap-realtime">(' . sprintf("%+d", $delay) . ')</span></span>';
                elseif ($delay >= -5)
                    $html .= '&nbsp;<span style="color:green;"><span class="ap-realtime">(' . sprintf("%+d", $delay) . ')</span></span>';
                else
                    $html .= '&nbsp;<span style="color:white; background-color:red"><span class="ap-realtime">(' . sprintf("%+d", $delay) . ')</span></span>';
            }
        }
    }

    //    $html .= '</span>';

    return $html;
}

function get_html_for_departure_board_quick_view_status_bar($services, $destination = '')
{
    $html = '';

    foreach ($services as $service) {
        if ($service->isInService && isset($service->AtThisLocation()->departure)) {
            $departureStatus = status_colour($service->AtThisLocation());
            $arrivalStatus = $departureStatus;

            if ($destination !== '' && $service->SubsequentLocations() !== null) {
                $destinationInfo = array_reduce(
                    $service->SubsequentLocations(),
                    function ($result, $item) use ($destination) {
                        return $item->genericId === $destination ? $item : $result;
                    }
                );

                $arrivalStatus = status_colour($destinationInfo, true);
            }

            $html .= '<span class="status-dot" style="background-image: linear-gradient(to right, ' . $departureStatus . ' ,' . $arrivalStatus . ')"></span>';
        }
    }
    $html .= "\n\n";

    return $html;
}

function get_html_for_arrival_board_quick_view_status_bar($services, $origin = '')
{
    $html = '';

    foreach ($services as $service) {
        if ($service->isInService && isset($service->AtThisLocation()->arrival)) {
            $arrivalStatus = status_colour($service->AtThisLocation(), true);
            $departureStatus = $arrivalStatus;

            if ($origin !== '' && $service->PreviousLocations() !== null) {
                $originInfo = array_reduce(
                    $service->PreviousLocations(),
                    function ($result, $item) use ($origin) {
                        return $item->genericId === $origin ? $item : $result;
                    }
                );

                $departureStatus = status_colour($originInfo);
            }

            $html .= '<span class="status-dot" style="background-image: linear-gradient(to right, ' . $departureStatus . ' ,' . $arrivalStatus . ')"></span>';
        }
    }
    $html .= "\n\n";

    return $html;
}

function get_html_for_board_calling_points(ServiceAtStop $info, array $callingPointList, bool $isDeparture, string $filterLoc, $searchOptions = null)
{
    $html = '';

    if (!empty($callingPointList)) {
        // Decide when to show stops
        $showStops = true;

        if (isset($filterLoc) && $filterLoc !== "" && !$isDeparture) {
            $showStops = false; // Not at highlighted stop yet
        }

        $html .= 'Calling at: ';
        foreach ($callingPointList as $index => $call) :

            if ($call->IsCommercialStop()) {
                if ($call->genericId === $filterLoc && !$isDeparture) {
                    // If at highlighted stop and we are showing arrivals, start showing stops from here
                    $showStops = true;
                }

                if ($showStops) {
                    $html .= get_html_for_board_calling_point($call, ($isDeparture ? (isset($call->arrival) ? $call->arrival : $call->departure) : (isset($call->departure) ? $call->departure : $call->arrival)), $filterLoc, $searchOptions);
                    if (count($callingPointList) === 1) {
                        $html .= ', only. ';
                    } elseif ($index === count($callingPointList) - 1 || $call->genericId === $filterLoc && $isDeparture) {
                        $html .= '. ';
                    } else {
                        $html .= ', ';
                    }
                }

                if ($call->genericId === $filterLoc && $isDeparture) {
                    // If at highlighted stop and we are showing departures, stop showing stops from here
                    break;
                }
            }
        endforeach;
    }

    if (isset($info->length))
        $html .= $info->length . '-car ';
    $html .= htmlspecialchars($info->operatorName) . ' ' . ($info->serviceMode === SERVICE_MODE_BUS_REPLACING_TRAIN ? 'rail replacement bus' : htmlspecialchars($info->serviceMode)) . ' ' . ($isDeparture ? ('from ' . $info->origin[0]->name) : ('to ' . $info->destination[0]->name));
    if (isset($info->formation)) {
        $html .= '. Formation: ';
        foreach ($info->formation->vehicles as $index => $coach) {
            $html .= $coach->identity . (isset($coach->comfortShortName) ? ('&nbsp;' . $coach->comfortShortName) : '');
            if ($coach->hasToilet) {
                $html .= $coach->isUniversalToilet ? '<i class="tiny material-icons">accessible</i>' : '<i class="tiny material-icons">wc</i>';
                if ($coach->toiletKnownOutOfService) $html .= '<i class="tiny material-icons">close</i>';
                else if (!$coach->toiletKnownInService && !$coach->toiletKnownOutOfService) $html .= '?';
            }
            if (isset($coach->realTimeLoading)) {
                $html .= ' <i class="material-icons">people</i>' . $coach->realTimeLoading . '%';
            }
            if ($index < count($info->formation->vehicles) - 1) $html .= '|';
        }
        if (isset($info->formation->averageLoading)) {
            $html .= ', <i class="material-icons">people_outline</i>&nbsp;exp.&nbsp;' . $info->formation->averageLoading . '%';
        }
        $html .= ' ';
    }
    $html .= '<span class="hide-on-small-only"> <span class="ap-board-service-trn">[' . htmlspecialchars($info->providerId['trn']) . ']</span></span>';
    if (isset($info->delayReason) && $info->MovementAtThisLocation($isDeparture)->state !== REAL_TIME_EXPECTED_ON_TIME) {
        $html .= ' <span class="ap-delay-info"><i class="material-icons">warning</i>&nbsp;' . htmlspecialchars($info->delayReason->reason) . '</span>';
    }
    if (isset($info->currentLocationDescription)) {
        $html .= ' <span class="ap-current-location"><i class="material-icons">location_on</i>&nbsp;' . $info->currentLocationDescription . '</span>';
    }

    return $html;
}

function get_html_for_board_calling_point(OperationalStopLocation $call, StopMovement $mov, $filterLoc, $searchOptions = null)
{
    $html = '';

    if ($call->genericId === $filterLoc) {
        $html .= '<span class="ap-your-stop">';
    }

    $html .= htmlspecialchars($call->name);

    if ($call->isCancelled) {
        $html .= ' at ' . $mov->scheduled->format(TIME_FORMAT) . ' <span style="color: red;">(cancelled)</span>';
    } else if ($mov->state === REAL_TIME_EXPECTED_ON_TIME) {
        $html .= ' at ' . $mov->scheduled->format(TIME_FORMAT);
    } else if ($mov->state === REAL_TIME_UNKNOWN_DELAY) {
        // Nothing - don't show scheduled times for a delayed service
    } else if ($mov->state === REAL_TIME_SCHEDULED_ONLY) {
        $html .= ' at ' . $mov->scheduled->format(TIME_FORMAT);
    } else {
        $delay = (int) minutes_later($mov->scheduled, $mov->realTime);
        $delayInfo = $delay >= DELAY_THRESHOLD_MINUTES ? '<span style="color:red;"> <span class="ap-realtime">(' . sprintf("%+d", $delay) . ')</span></span>' : '';
        $html .= ' at ' . $mov->realTime->format(TIME_FORMAT) . $delayInfo;
    }

    if (isset($call->associations)) {
        foreach ($call->associations as $association) {
            switch ($association->associationType) {
                case ASSOCIATION_TYPE_SPLIT:
                    $html .= ' (splits from service to <a href="/service/?serviceId=' . urlencode($association->otherServiceGenericId) . '&atLoc=' . urlencode($call->genericId) . search_options($searchOptions) . '">' . $association->otherServiceDistalLoc->name . '</a>)';
                    break;
                case ASSOCIATION_TYPE_CONNECTION:
                    $html .= ' (change here for <a href="/service/?serviceId=' . urlencode($association->otherServiceGenericId) . '&atLoc=' . urlencode($call->genericId) . search_options($searchOptions) . '">' . $association->otherServiceDistalLoc->name . '</a>)';
                    break;
                case ASSOCIATION_TYPE_JOIN:
                    $html .= ' (joins with service from <a href="/service/?serviceId=' . urlencode($association->otherServiceGenericId) . '&atLoc=' . urlencode($call->genericId) . search_options($searchOptions) . '">' . $association->otherServiceDistalLoc->name . '</a>)';
                    break;
            }
        }
    }

    if ($call->genericId === $filterLoc) {
        $html .= '</span>';
    }

    return $html;
}

function get_html_for_service_stop(Service $service, OperationalStopLocation $call, bool $showArrival = false, $searchOptions = null)
{
    $html = '';

    $mov = $showArrival ? (isset($call->arrival) ? $call->arrival : $call->departure) : (isset($call->departure) ? $call->departure : $call->arrival);
    $timeNote = '';
    if ($showArrival && !isset($call->arrival)) {
        $timeNote = ' (dep.)';
    }
    if (!$showArrival && !isset($call->departure)) {
        $timeNote = ' (arr.)';
    }
    $passengerNote = '';
    switch ($call->platformOrStopSpecial) {
        case STOP_TYPE_SET_DOWN_ONLY:
            $passengerNote = '<span class="ap-train-use-note"> (stops to set-down only)</span>';
            break;
        case STOP_TYPE_PICK_UP_ONLY:
            $passengerNote = '<span class="ap-train-use-note"> (stops to pick-up only)</span>';
            break;
        case STOP_TYPE_RAILWAY_REQUEST:
            $passengerNote = '<span class="ap-train-use-note"> (stops on request)</span>';
            break;
        case STOP_TYPE_UNADVERTISED:
            $passengerNote = '<span class="ap-train-use-note"> (unadvertised stop)</span>';
            break;
    }

    $icon = $mov->IsConfirmed() ? '<i class="material-icons left ap-realtime">check</i>' : '';
    if (isset($call->arrival) && isset($call->departure) && $call->arrival->IsConfirmed() && $call->departure->IsEstimated()) {
        $icon = '<i class="material-icons left ap-realtime">location_on</i>';
    }

    // New row
    $html .= '<tr class="ap-service-stop" id="' . $call->genericId . '-' . $mov->scheduled->format('Y-m-d\TH:i:s') . '">';

    // Time column
    $html .= '<td class="ap-service-time" style="background-color: ' . status_colour($call, $showArrival) . '">';

    if ($call->isCancelled) {
        $html .= '<i class="material-icons right">error</i>Cancelled' . (isset($call->lateness) ? (' <span class="ap-lateness-sec">(pass ' . sprintf('%+d:%02d', $call->lateness / 60, $call->lateness % 60) . ')</span>') : '');
    } else if (isset($call->lateness)) {
        $html .= $mov->realTime->format(TIME_FORMAT) . $timeNote . ' <span class="ap-lateness-sec">(' . sprintf('%+d:%02d', $call->lateness / 60, $call->lateness % 60) . ')</span>';
    } else if ($mov->state === REAL_TIME_EXPECTED_ON_TIME || $mov->state === REAL_TIME_SCHEDULED_ONLY) {
        $html .= $mov->scheduled->format(TIME_FORMAT) . $timeNote;
    } else if ($mov->state === REAL_TIME_UNKNOWN_DELAY) {
        $html .= '<i class="material-icons right">warning</i>Delayed';
    } else {
        $delay = minutes_later($mov->scheduled, $mov->realTime);
        $html .= $mov->realTime->format(TIME_FORMAT) . $timeNote . ' <span class="ap-realtime">(' . sprintf("%+d", $delay) . ')</span>';
    }

    $html .= '</td>';

    // Platform column
    $html .= '<td class="ap-service-platform">';
    if (isset($call->platformOrStop) && $call->platformOrStop !== 'BUS' && !$call->isCancelled) {
        $html .= '<span class="ap-platform-' . $call->platformOrStopState . '">' . $call->platformOrStop . '</span>';
    }
    $html .= '</td>';

    // Location column - only show a link to connecting services if it's an arrival, and in the future
    if ($showArrival && (isset($mov->realTime) && in_num_minutes($mov->realTime) >= 0 || $mov->state === REAL_TIME_UNKNOWN_DELAY) && $service->provider->supportsConnections()) {
        $html .= '<td class="ap-service-station"><a onclick="showConnectionsInline(\'' . $call->genericId . '-' . $mov->scheduled->format('Y-m-d\TH:i:s') . '\', \'/php/html/locations/simplified-connections.php/?loc=' . $call->genericId . '&serviceId=' . $service->genericId . '&scheduledArrival=' . $mov->scheduled->format('Y-m-d\TH:i:s') . (isset($service->stopIndexOfInterest) ? ('&arrivalBoardedIndex=' . $service->stopIndexOfInterest) : '') . search_options($searchOptions) . '\');" href="javascript:void(0);">' . htmlspecialchars($call->name) . $passengerNote . $icon . '</a>';
    } else {
        $html .= '<td class="ap-service-station">' . htmlspecialchars($call->name) . $passengerNote . $icon;
    }

    if (isset($call->associations)) {
        foreach ($call->associations as $association) {
            switch ($association->associationType) {
                case ASSOCIATION_TYPE_SPLIT:
                    $html .= ' (divides into service to <a href="/service/?serviceId=' . urlencode($association->otherServiceGenericId) . '&atLoc=' . urlencode($call->genericId) . search_options($searchOptions) . '">' . $association->otherServiceDistalLoc->name . '</a>)';
                    break;
                case ASSOCIATION_TYPE_CONNECTION:
                    $html .= ' (change here for to <a href="/service/?serviceId=' . urlencode($association->otherServiceGenericId) . '&atLoc=' . urlencode($call->genericId) . search_options($searchOptions) . '">' . $association->otherServiceDistalLoc->name . '</a>)';
                    break;
                case ASSOCIATION_TYPE_JOIN:
                    $html .= ' (joins with service from <a href="/service/?serviceId=' . urlencode($association->otherServiceGenericId) . '&atLoc=' . urlencode($call->genericId) . search_options($searchOptions) . '">' . $association->otherServiceDistalLoc->name . '</a>)';
                    break;
            }
        }
    }

    if ($call->formation) {
        if (isset($call->formation->averageLoading) && $call->formation->averageLoading > 0) {
            $html .= ' <i class="material-icons">people_outline</i>&nbsp;exp.&nbsp;' . $call->formation->averageLoading . '%';
        }
    }

    $html .= '</td>';
    // End row
    $html .= "</tr>\n\n";

    return $html;
}

function get_html_for_service_stop_for_req_location(OperationalStopLocation $info, $searchOptions = null)
{
    $html = '';

    $platformAlreadyShown = false;
    // Arrival time
    if (isset($info->arrival)) {
        $html .= '<tr class="ap-service-stop ap-your-stop"><td class="ap-service-time" style="background-color: ' . status_colour($info, true) . '">';
        if ($info->isCancelled) {
            $html .= '<i class="material-icons right">error</i>Cancelled' . (isset($info->lateness) ? (' <span class="ap-lateness-sec">(pass ' . sprintf('%+d:%02d', $info->lateness / 60, $info->lateness % 60) . ')</span>') : '');
        } else if (isset($info->lateness)) {
            $html .= $info->arrival->realTime->format(TIME_FORMAT) . ' <span class="ap-lateness-sec">(' . sprintf('%+d:%02d', $info->lateness / 60, $info->lateness % 60) . ')</span>';
        } else  if ($info->arrival->state === REAL_TIME_EXPECTED_ON_TIME || $info->arrival->state === REAL_TIME_SCHEDULED_ONLY) {
            $html .= $info->arrival->scheduled->format(TIME_FORMAT);
        } else if ($info->arrival->state === REAL_TIME_UNKNOWN_DELAY) {
            $html .= '<i class="material-icons right">warning</i>Delayed';
        } else {
            $delay = minutes_later($info->arrival->scheduled, $info->arrival->realTime);
            $html .= $info->arrival->realTime->format(TIME_FORMAT) . ' <span class="ap-realtime">(' . sprintf("%+d", $delay) . ')</span>';
        }

        $html .= '</td><td class="ap-service-platform"></td><td class="ap-service-station">' . (!$info->isCancelled ? (($info->arrival->IsConfirmed() ? 'Arrived' : 'Arriving') . ((isset($info->platformOrStop) && $info->platformOrStop !== 'BUS') ? (' at platform <span class="ap-platform-' . $info->platformOrStopState . '">' . $info->platformOrStop . '</span>') : '') . ($info->arrival->IsConfirmed() ? '<i class="material-icons left">check</i>' : '') . '</td>') : '') . '</tr>';
        $platformAlreadyShown = true;
    }

    // Associations
    if (isset($info->associations)) {
        foreach ($info->associations as $association) {
            $html .= '<tr class="ap-service-stop ap-your-stop"><td class="ap-service-time" style="background-color: ' . status_colour($info, true) . '"></td><td class="ap-service-platform"></td><td class="ap-service-station">';
            switch ($association->associationType) {
                case ASSOCIATION_TYPE_SPLIT:
                    $html .= 'Splits from service to <a href="/service/?serviceId=' . urlencode($association->otherServiceGenericId) . '&atLoc=' . urlencode($info->genericId) . search_options($searchOptions) . '">' . $association->otherServiceDistalLoc->name . '</a>';
                    break;
                case ASSOCIATION_TYPE_CONNECTION:
                    $html .= 'Change here for <a href="/service/?serviceId=' . urlencode($association->otherServiceGenericId) . '&atLoc=' . urlencode($info->genericId) . search_options($searchOptions) . '">' . $association->otherServiceDistalLoc->name . '</a>)';
                    break;
                case ASSOCIATION_TYPE_JOIN:
                    $html .= 'Joins with service from <a href="/service/?serviceId=' . urlencode($association->otherServiceGenericId) . '&atLoc=' . urlencode($info->genericId) . search_options($searchOptions) . '">' . $association->otherServiceDistalLoc->name . '</a>';
                    break;
            }
            $html .= '</td></tr>';
        }
    }

    // Formation
    if (isset($info->formation->averageLoading) && $info->formation->averageLoading > 0) {
        $html .= '<tr class="ap-service-stop ap-your-stop"><td class="ap-service-time" style="background-color: ' . status_colour($info, true) . '"></td><td class="ap-service-platform"></td><td class="ap-service-station">';
        $html .= '<i class="material-icons">people_outline</i>&nbsp;exp.&nbsp;' . $info->formation->averageLoading . '%';
        $html .= '</td></tr>';
    }
    // Formation ends

    // Departure time
    if (isset($info->departure)) {
        $html .= '<tr class="ap-service-stop ap-your-stop"><td class="ap-service-time" style="background-color: ' . status_colour($info) . '">';
        if ($info->isCancelled) {
            $html .= '<i class="material-icons right">error</i>Cancelled' . (isset($info->lateness) ? (' <span class="ap-lateness-sec">(pass ' . sprintf('%+d:%02d', $info->lateness / 60, $info->lateness % 60) . ')</span>') : '');
        } else if (isset($info->lateness)) {
            $html .= $info->departure->realTime->format(TIME_FORMAT) . ' <span class="ap-lateness-sec">(' . sprintf('%+d:%02d', $info->lateness / 60, $info->lateness % 60) . ')</span>';
        } else if ($info->departure->state === REAL_TIME_EXPECTED_ON_TIME || $info->departure->state === REAL_TIME_SCHEDULED_ONLY) {
            $html .= $info->departure->scheduled->format(TIME_FORMAT);
        } else if ($info->departure->state === REAL_TIME_UNKNOWN_DELAY) {
            $html .= '<i class="material-icons right">warning</i>Delayed';
        } else {
            $delay = minutes_later($info->departure->scheduled, $info->departure->realTime);
            $html .= $info->departure->realTime->format(TIME_FORMAT) . ' <span class="ap-realtime">(' . sprintf("%+d", $delay) . ')</span>';
        }
        $html .= '</td><td class="ap-service-platform"></td><td class="ap-service-station">' . (!$info->isCancelled ? (($info->departure->IsConfirmed() ? 'Departed' : 'Departing') . (!$platformAlreadyShown && (isset($info->platformOrStop) && $info->platformOrStop !== 'BUS') ? (' from platform <span class="ap-platform-' . $info->platformOrStopState . '">' . $info->platformOrStop . '</span>') : '') . ($info->departure->IsConfirmed() ? '<i class="material-icons left">check</i>' : '') . '</td>') : '') . '</tr>';
    }

    return $html;
}

function status_colour(StopLocation $stopLoc, bool $isArrival = false)
{
    $movement = $isArrival ? ($stopLoc->arrival ?? $stopLoc->departure) : ($stopLoc->departure ?? $stopLoc->arrival);

    if ($stopLoc->isCancelled)
        $statusColour = 'rgb(0, 255, 255)'; // cyan
    else {
        if ($movement->state === REAL_TIME_EXPECTED_ON_TIME || $movement->state === REAL_TIME_EARLY)
            $statusColour = 'rgb(0, 255, 0)';          // green
        else if ($movement->state === REAL_TIME_UNKNOWN_DELAY)
            $statusColour = 'rgb(255, 0, 255)';   // magenta
        else if ($movement->state === REAL_TIME_SCHEDULED_ONLY || $movement->state === REAL_TIME_ONLY)
            $statusColour = 'transparent';   // don't show anything
        else {
            $delay = minutes_later($movement->scheduled, $movement->realTime);
            if ($delay < 0) $delay = 0;
            // Have to work it out!
            $awfulness = 1 - 10 / (10 + $delay);

            $r = 512 * $awfulness;
            if ($r > 255)
                $r = 255;
            $g = 512 - (512 * $awfulness);
            if ($g > 255)
                $g = 255;

            $statusColour = 'rgb(' . (int) $r . ', ' . (int) $g . ', 0)';
        }
    }

    return $statusColour;
}

function get_html_for_simple_origs_or_dests($service, bool $showDestinations)
{
    $html = '';

    $origsDests = $showDestinations ? $service->destination : $service->origin;
    foreach ($origsDests as $index => $origOrDest) :
        if ($service->isCircular) {
            $html .= htmlspecialchars($origOrDest->name) . ' (Circular)';
        } else {
            $html .= htmlspecialchars($origOrDest->name);
        }
        if (isset($origOrDest->via))
            $html .= ' ' . htmlspecialchars($origOrDest->via);

        if ($index < count($origsDests) - 1)
            $html .= ' and ';
    endforeach;

    return $html;
}

function get_html_for_orig_or_dest_info($service, bool $showDestinations, bool $isBoardDisplay)
{
    $html = '';

    if ($service->serviceMode === SERVICE_MODE_TRAIN || $service->serviceMode === SERVICE_MODE_BUS_REPLACING_TRAIN) { // FIXME: Really - is it NR?
        $origsDests = $showDestinations ? $service->destination : $service->origin;
        foreach ($origsDests as $index => $origOrDest) :
            if ($service instanceof ServiceAtStop && $isBoardDisplay && $showDestinations) {
                // Does the service end here?
                if ($service->stopIndexOfInterest === count($service->locations) - 1) {
                    $html .= 'Terminates here from ' . $service->origin[0]->name . (!$service->isInService ? (' [' . $service->providerId['trn'] . ']') : '');
                    continue;
                }
                // Does it end here because later stops have been cancelled?
                if (isset($service->cancellationReason) && !$service->AtThisLocation()->isCancelled) {   // FIXME Would prefer cancelled / part-cancelled / OK enumeration
                    $nowTerminatesHere = true;
                    foreach (array_slice($service->locations, $service->stopIndexOfInterest + 1) as $laterLoc) {
                        if ($laterLoc instanceof CommercialStopLocation && !$laterLoc->isCancelled) {
                            $nowTerminatesHere = false;
                        }
                    }
                    if ($nowTerminatesHere) {
                        $html .= 'Will now terminate here, from ' . $service->origin[0]->name . (!$service->isInService ? (' [' . $service->providerId['trn'] . ']') : '');
                        continue;
                    }
                }
            }

            if (!$service->isInService) {
                $html .= 'Empty ' . ($showDestinations ? 'to' : 'from') . ' ' . htmlspecialchars($origOrDest->name) . '<span class="hide-on-small-only"> [' . $service->providerId['trn'] . ']</span>';
            } else if ($service->isCircular) {
                $html .= htmlspecialchars($origOrDest->name) . ' (Circular)';
            } else {
                $html .= htmlspecialchars($origOrDest->name);
            }
            if (isset($origOrDest->via))
                $html .= ' ' . htmlspecialchars($origOrDest->via);

            if ($index < count($origsDests) - 1)
                $html .= ' and ';
        endforeach;
    } else {
        $html .= htmlspecialchars($service->destination->name ?? $service->destination[0]->name);
    }

    return $html;
}

function get_html_for_formation($formation)
{
    $html = '';

    $html .= 'Formation: ';
    foreach ($formation->vehicles as $index => $coach) {
        $html .= $coach->identity . (isset($coach->comfortShortName) ? ('&nbsp;' . $coach->comfortShortName) : '');
        if ($coach->hasToilet) {
            $html .= $coach->isUniversalToilet ? '<i class="tiny material-icons">accessible</i>' : '<i class="tiny material-icons">wc</i>';
            if ($coach->toiletKnownOutOfService) $html .= '<i class="tiny material-icons">close</i>';
            else if (!$coach->toiletKnownInService && !$coach->toiletKnownOutOfService) $html .= '?';
        }
        if (isset($coach->realTimeLoading)) {
            $html .= '<i class="material-icons">people</i>' . $coach->realTimeLoading . '%';
        }
        if ($index < count($formation->vehicles) - 1) $html .= '|';
    }

    if (isset($formation->note)) {
        $html .= ' ' . $formation->note;
    }


    return $html;
}

function get_html_for_transport_mode($service)
{
    return $service->serviceMode == SERVICE_MODE_BUS_REPLACING_TRAIN ? 'replacement bus' : $service->serviceMode;
}

function concatenateWithPunctuation($textItems)
{
    if (!is_array($textItems)) return $textItems;
    if (count($textItems) == 0) return '';
    if (count($textItems) == 1) return $textItems[0];

    $concatenated = '';
    for ($key = 0; $key < count($textItems) - 1; $key++) {
        $concatenated .= $textItems[$key] . ($key < count($textItems) - 2 ? ', ' : '');
    }
    $concatenated .= ' and ' . $textItems[count($textItems) - 1];

    return $concatenated;
}

function search_options($searchOptions)
{
    $urlOpts = '';

    if (isset($searchOptions)) {
        if (is_array($searchOptions)) {
            $searchOptions = array_unique($searchOptions);
            foreach ($searchOptions as $searchOption) {
                $urlOpts .= '&opt[]=' . urlencode($searchOption);
            }
        } else {
            $urlOpts = '&opt=' . urlencode($searchOptions);
        }
    }

    return $urlOpts;
}
