/*
    SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "timelineelement.h"

#include "locationhelper.h"
#include "reservationmanager.h"
#include "timelinemodel.h"
#include "transfer.h"

#include <KItinerary/Event>
#include <KItinerary/LocationUtil>
#include <KItinerary/Place>
#include <KItinerary/Reservation>
#include <KItinerary/SortUtil>
#include <KItinerary/Visit>

#include <KPublicTransport/Journey>
#include <KPublicTransport/Stopover>

using namespace KItinerary;

static TimelineElement::ElementType elementType(const QVariant &res)
{
    if (JsonLd::isA<FlightReservation>(res)) { return TimelineElement::Flight; }
    if (JsonLd::isA<LodgingReservation>(res)) { return TimelineElement::Hotel; }
    if (JsonLd::isA<TrainReservation>(res)) { return TimelineElement::TrainTrip; }
    if (JsonLd::isA<BusReservation>(res)) { return TimelineElement::BusTrip; }
    if (JsonLd::isA<BoatReservation>(res)) { return TimelineElement::BoatTrip; }
    if (JsonLd::isA<FoodEstablishmentReservation>(res)) { return TimelineElement::Restaurant; }
    if (JsonLd::isA<TouristAttractionVisit>(res)) { return TimelineElement::TouristAttraction; }
    if (JsonLd::isA<EventReservation>(res)) { return TimelineElement::Event; }
    if (JsonLd::isA<RentalCarReservation>(res)) { return TimelineElement::CarRental; }
    return {};
}

TimelineElement::TimelineElement() = default;

TimelineElement::TimelineElement(TimelineModel *model, TimelineElement::ElementType type, const QDateTime &dateTime, const QVariant &data)
    : dt(dateTime)
    , elementType(type)
    , m_content(data)
    , m_model(model)
{
}

TimelineElement::TimelineElement(TimelineModel *model, const QString& resId, const QVariant& res, TimelineElement::RangeType rt)
    : dt(relevantDateTime(res, rt))
    , elementType(::elementType(res))
    , rangeType(rt)
    , m_content(resId)
    , m_model(model)
{
}

TimelineElement::TimelineElement(TimelineModel *model, const ::Transfer &transfer)
    : dt(transfer.anchorTime())
    , elementType(Transfer)
    , rangeType(SelfContained)
    , m_content(QVariant::fromValue(transfer))
    , m_model(model)
{
}

static bool operator<(TimelineElement::RangeType lhs, TimelineElement::RangeType rhs)
{
    static const int order_map[] = { 1, 0, 2 };
    return order_map[lhs] < order_map[rhs];
}

bool TimelineElement::operator<(const TimelineElement &other) const
{
    if (dt == other.dt) {
        if (rangeType == RangeEnd || other.rangeType == RangeEnd) {
            if (rangeType == RangeBegin || other.rangeType == RangeBegin) {
                return rangeType < other.rangeType;
            }
            return elementType > other.elementType;
        }
        return elementType < other.elementType;
    }
    return dt < other.dt;
}

bool TimelineElement::operator<(const QDateTime &otherDt) const
{
    return dt < otherDt;
}

bool TimelineElement::operator==(const TimelineElement &other) const
{
    if (elementType != other.elementType || rangeType != other.rangeType || dt != other.dt || batchId() != other.batchId()) {
        return false;
    }

    switch (elementType) {
        case Transfer:
        {
            const auto lhsT = m_content.value<::Transfer>();
            const auto rhsT = other.m_content.value<::Transfer>();
            return lhsT.alignment() == rhsT.alignment();
        }
        default:
            return true;
    }
}

bool TimelineElement::isReservation() const
{
    switch (elementType) {
        case Flight:
        case TrainTrip:
        case CarRental:
        case BusTrip:
        case BoatTrip:
        case Restaurant:
        case TouristAttraction:
        case Event:
        case Hotel:
            return true;
        case Undefined:
        case TodayMarker:
        case TripGroup:
        case WeatherForecast:
        case LocationInfo:
        case Transfer:
            return false;
    }

    Q_UNREACHABLE();
    return false;
}

QString TimelineElement::batchId() const
{
    if (isReservation()) {
        return m_content.toString();
    }
    if (elementType == Transfer) {
        return m_content.value<::Transfer>().reservationId();
    }
    return {};
}

QVariant TimelineElement::content() const
{
    return m_content;
}

void TimelineElement::setContent(const QVariant& content)
{
    m_content = content;
}

QDateTime TimelineElement::relevantDateTime(const QVariant &res, TimelineElement::RangeType range)
{
    if (range == TimelineElement::RangeBegin || range == TimelineElement::SelfContained) {
        return SortUtil::startDateTime(res);
    }
    if (range == TimelineElement::RangeEnd) {
        return SortUtil::endDateTime(res);
    }

    return {};
}

bool TimelineElement::isLocationChange() const
{
    if (isReservation()) {
        // ### can be done without reservation lookup for some of the reservation element types
        const auto res = m_model->m_resMgr->reservation(batchId());
        return LocationUtil::isLocationChange(res);
    }

    if (elementType == Transfer) {
        return m_content.value<::Transfer>().state() == Transfer::Selected;
    }

    return false;
}

bool TimelineElement::isTimeBoxed() const
{
    switch (elementType) {
        case Undefined:
        case TodayMarker:
        case TripGroup:
        case WeatherForecast:
        case LocationInfo:
            return false;
        case Transfer:
            return m_content.value<::Transfer>().state() == Transfer::Selected;
        case Flight:
        case TrainTrip:
        case BusTrip:
        case BoatTrip:
            return true;
        case Hotel:
        case CarRental:
            return false;
        case Restaurant:
        case TouristAttraction:
        case Event:
            return SortUtil::endDateTime(m_model->m_resMgr->reservation(batchId())).isValid();
    }
    return false;
}

bool TimelineElement::isCanceled() const
{
    if (isReservation()) {
        const auto res = m_model->m_resMgr->reservation(batchId());
        return JsonLd::canConvert<Reservation>(res) && JsonLd::convert<Reservation>(res).reservationStatus() == Reservation::ReservationCancelled;
    }

    // TODO transfers with Disruption::NoService
    return false;
}

QString TimelineElement::destinationCountry() const
{
    if (isReservation()) {
        const auto res = m_model->m_resMgr->reservation(batchId());
        return LocationHelper::destinationCountry(res);
    }

    if (elementType == Transfer) {
        const auto transfer = m_content.value<::Transfer>();
        if (transfer.state() == Transfer::Selected) {
            const auto &sections = transfer.journey().sections();
            if (!sections.empty()) {
                return sections.back().arrival().stopPoint().country();
            }
        }
    }

    return {};
}

KItinerary::GeoCoordinates TimelineElement::destinationCoordinates() const
{
    if (isReservation()) {
        const auto res = m_model->m_resMgr->reservation(batchId());
        if (LocationUtil::isLocationChange(res)) {
            return LocationUtil::geo(LocationUtil::arrivalLocation(res));
        }
        return LocationUtil::geo(LocationUtil::location(res));
    }

    if (elementType == Transfer) {
        const auto transfer = m_content.value<::Transfer>();
        if (transfer.state() == Transfer::Selected) {
            const auto &sections = transfer.journey().sections();
            if (!sections.empty()) {
                const auto loc = sections.back().arrival().stopPoint();
                return KItinerary::GeoCoordinates(loc.latitude(), loc.longitude());
            }
        }
    }

    return {};
}

QDateTime TimelineElement::endDateTime() const
{
    if (isReservation()) {
        const auto res = m_model->m_resMgr->reservation(batchId());
        return SortUtil::endDateTime(res);
    }

    if (elementType == Transfer) {
        const auto transfer = m_content.value<::Transfer>();
        if (transfer.state() == Transfer::Selected) {
            return transfer.journey().scheduledArrivalTime();
        }
    }

    return {};
}
