#pragma once

#include "common.h"

#include <maps/libs/chrono/include/time_point.h>
#include <maps/libs/geolib/include/conversion.h>
#include <maps/libs/geolib/include/heading.h>
#include <maps/libs/geolib/include/point.h>
#include <maps/libs/sql_chemistry/include/gateway_access.h>

#include <optional>
#include <string>
#include <tuple>
#include <vector>

namespace maps::mrc::db {

class TrackPoint {
public:
    TrackPoint() = default; //FIXME: move default ctor to private section

    TId id() const { return id_; }

    const std::string& sourceId() const { return sourceId_; }

    geolib3::Point2 geodeticPos() const {
        return geolib3::convertMercatorToGeodetic(mercatorPos());
    }

    geolib3::Point2 mercatorPos() const { return mercatorPos_; }

    chrono::TimePoint timestamp() const { return timestamp_; }

    std::optional<double> accuracyMeters() const { return accuracy_; }

    /// in degrees relative to the true north, clockwise
    std::optional<geolib3::Heading> heading() const {
        if (heading_) {
            return geolib3::Heading(*heading_);
        } else {
            return std::nullopt;
        }
    }

    std::optional<double> speedMetersPerSec() const { return speed_; }

    bool isAugmented() const { return isAugmented_.value_or(false); }

    const std::optional<TId>& assignmentId() const { return assignmentId_; }

    TrackPoint& setSourceId(std::string sourceId) {
        sourceId_ = std::move(sourceId);
        return *this;
    }

    TrackPoint& setGeodeticPos(const geolib3::Point2& pos) {
        return setMercatorPos(geolib3::convertGeodeticToMercator(pos));
    }

    TrackPoint& setMercatorPos(const geolib3::Point2& pos) {
        mercatorPos_ = pos;
        return *this;
    }

    TrackPoint& setTimestamp(chrono::TimePoint timePoint) {
        timestamp_ = timePoint;
        return *this;
    }

    TrackPoint& setAccuracyMeters(double accuracy) {
        accuracy_ = accuracy;
        return *this;
    }

    TrackPoint& setHeading(geolib3::Heading heading) {
        heading_ = geolib3::normalize(heading).value();
        return *this;
    }

    TrackPoint& resetHeading() {
        heading_ = std::nullopt;
        return *this;
    }

    TrackPoint& setSpeedMetersPerSec(double speed) {
        speed_ = speed;
        return *this;
    }

    TrackPoint& setIsAugmented(bool isAugmented) {
        isAugmented_ = isAugmented;
        return *this;
    }

    TrackPoint& setAssignmentId(TId assignmentId) {
        assignmentId_ = assignmentId;
        return *this;
    }

private:
    friend class sql_chemistry::GatewayAccess<TrackPoint>;

    template <typename T>
    static auto introspect(T& t) {
        return std::tie(t.id_, t.sourceId_, t.mercatorPos_, t.timestamp_, t.accuracy_,
            t.heading_, t.speed_, t.isAugmented_, t.assignmentId_);
    }

    TId id_{0};
    std::string sourceId_{};
    geolib3::Point2 mercatorPos_{};
    chrono::TimePoint timestamp_{};
    std::optional<double> accuracy_{};
    //FIXME: use tagged type after https://st.yandex-team.ru/MAPSCORE-4496
    std::optional<double> heading_{};
    std::optional<double> speed_{};
    std::optional<bool> isAugmented_{};
    std::optional<TId> assignmentId_{};

public:
    auto introspect() const { return introspect(*this); }
};

using TrackPoints = std::vector<TrackPoint>;

} // namespace maps::mrc::db
