#ifndef CALCULATOR_FUNCTIONS_H_INCLUDED
#define CALCULATOR_FUNCTIONS_H_INCLUDED

#include <iostream>
#include <optional>

#include "configuration.h"
#include "calculate_price.h"
#include "payment_method.h"
#include "ticket.h"
#include "tariff_time_range.h"

#include <QDateTime>
using namespace std;

class Calculator {
    mutable QList<int> m_timeSteps;
    mutable QList<int> m_priceSteps;

protected:
    explicit Calculator() = default;

public:
    Calculator(Calculator const &other) = delete;
    void operator=(Calculator const &) = delete;

    static Calculator &GetInstance() {
        static Calculator c;
        return c;
    }

    void ResetTimeSteps() { m_timeSteps.clear(); }
    QList<int> timeSteps() const { return m_timeSteps; }

    void ResetPriceSteps() { m_priceSteps.clear(); }
    QList<int> priceSteps() const { return m_priceSteps; }

    CalcState isParkingAllowed(Configuration const *cfg, QDateTime const &start);

	/// <summary>
	/// Gets duration in seconds from cost
	/// </summary>
	/// <param name="tariff_cfg">Pointer to configuration</param>
	/// <param name="vehicle_type">Type of vehicle</param>
	/// <param name="start_datetime">Date/time of payment to be conducted in ISO8601 format (e.g. 2022-12-25T08:00:00Z)</param>
	/// <param name="price"></param>
	/// <returns>Returns duration in seconds (data type: double)</returns>
	std::string GetDurationFromCost(Configuration* cfg, uint8_t vehicle_type, char const* start_datetime, double price, bool nextDay = false, bool prepaid = false);

	/// <summary>
	///  Gets cost from duration in seconds
	/// </summary>
	/// <param name="tariff_cfg">Pointer to configuration</param>
	/// <param name="vehicle_type">Type of vehicle</param>
	/// <param name="start_datetime">Date/time of payment to be conducted in ISO8601 format (e.g. 2022-12-25T08:00:00Z) </param>
    /// <param name="end_datetime">Date/time of park end to be conducted in ISO8601 format (e.g. 2022-12-25T08:00:00Z) </param>
	/// <param name="durationMin">Duration of parking in minutes</param>
	/// <returns>Returns cost (data type: double)</returns>
    double GetCostFromDuration(Configuration* cfg, uint8_t vehicle_type, QDateTime &start_datetime, QDateTime & end_datetime, int durationMin, bool nextDay = false, bool prepaid = false);

    // Daily ticket
    QDateTime GetDailyTicketDuration(Configuration* cfg, const QDateTime start_datetime, uint8_t payment_option, bool carry_over);
    std::optional<struct price_t> GetDailyTicketPrice(Configuration* cfg, QDateTime const &startDatetime, QDateTime &endTime, PERMIT_TYPE permitType);

    //
    // helper function to find time steps for a tariff with PaymentMethod::Steps
    // (e.g. Schoenau/Koenigsee)
    //
    QList<int> GetTimeSteps(Configuration *cfg) const;
    QList<int> GetSteps(Configuration *cfg) const { return GetTimeSteps(cfg); }

    QList<int> GetPriceSteps(Configuration *cfg) const;

    // additional helper functions
    bool noSpecialDays(Configuration const *cfg) const {
        return (cfg->SpecialDays.size() == 0) && (cfg->SpecialDaysWorktime.size() == 0);
    }
    bool specialDays(Configuration const *cfg) const {
        return !noSpecialDays(cfg);
    }
    bool tariffIs24_7(Configuration const *cfg) const {
        return (cfg->YearPeriod.size() == 0 &&
                cfg->SpecialDays.size() == 0 &&
                cfg->SpecialDaysWorktime.size() == 0);
    }

// testing public:
    // Introduced for PaymentMethod::Steps (e.g. Schoenau)
    // For tariff of following structure: only steps, no special days, nonstop.
    uint32_t GetCostFromDuration(Configuration *cfg, QDateTime const &start, quint64 durationMinutes) const;
    uint32_t GetCostFromDuration(Configuration *cfg, QDateTime const &start, QDateTime const &end) const;

private:
    Ticket private_GetCostFromDuration(Configuration const* cfg,
                                       QDateTime const &start,
                                       int durationMinutes,
                                       bool prepaid = false);
    Ticket private_GetDurationFromCost(Configuration *cfg,
                                       QDateTime const &start,
                                       uint32_t price,
                                       bool prepaid = false);

    bool checkDurationMinutes(int minParkingTime, int maxParkingTime,
                              int durationMinutes);

    //
    uint32_t GetPriceForTimeStep(Configuration *cfg, int timeStep) const;
    uint32_t GetPriceForStep(Configuration *cfg, int step) const {
        return GetPriceForTimeStep(cfg, step);
    }
    uint32_t GetDurationForPrice(Configuration *cfg, int price) const;
    uint32_t GetStepForPrice(Configuration *cfg, int price) const {
        return GetDurationForPrice(cfg, price);
    }

    int findWorkTimeRange(QDateTime const &dt,
                          QScopedArrayPointer<TariffTimeRange> const &worktime,
                          size_t size);
    int findNextWorkTimeRange(QDateTime const &dt,
                              QScopedArrayPointer<TariffTimeRange> const &worktime,
                              size_t size);
};

#endif // CALCULATOR_FUNCTIONS_H_INCLUDED