#include "calculate_parking_tickets_algorithms.h"
#include "calculate_parking_tickets_global.h"
#include "calculate_parking_tickets_tariff_step.h"
#include "calculate_parking_tickets_utils.h"
#include "calculate_parking_tickets_tariff.h"
#include "calculate_parking_tickets_day.h"

#include <QDebug>
#include <QDateTime>
#include <cmath>


/*  BEGRIFFE
    bezahltes Parken am Ende der Parkdauer vor Einsteigen ins Auto:
        pay on foot parking
    bezahltes Parken am Anfang der Parkdauer und Quittung an der Windschutzscheibe
        pay and display parking
    Parkdauer
        parking time
    Parkschein
        parking voucher
    Parkgutschein
        parking voucher
    Maximale Parkdauer
        maximal parking time
*/

#if 0
    bool get_price(parking_tariff_t const *tariff, QDateTime const &qdt,
                   uint32_t &price_units) {
        QDate const &qdate = qdt.date();
        QTime const &qtime = qdt.time();
        qDebug() << qdate << qtime;
        if (!TUtils::isHoliday(tariff, qdate)) {
            int const dayOfWeek = qdate.dayOfWeek();
            switch (dayOfWeek) {
            case Qt::Monday:
                return Monday::price(tariff, qtime, &price_units);
            case Qt::Tuesday:
                return Tuesday::price(tariff, qtime, &price_units);
            case Qt::Wednesday:
                return Wednesday::price(tariff, qtime, &price_units);
            case Qt::Thursday:
                return Thursday::price(tariff, qtime, &price_units);
            case Qt::Friday:
                return Friday::price(tariff, qtime, &price_units);
            case Qt::Saturday:
                return Saturday::price(tariff, qtime, &price_units);
            case Qt::Sunday:
                return Sunday::price(tariff, qtime, &price_units);
            }
        }
        return false;
        // return Holiday::price(tariff, qtime, &price_units);
    }
#endif

#ifdef __cplusplus
extern  "C" {
#endif

static minute_t computeFreeOfCharge(parking_tariff_t const *tariff,
                                    minute_t startMinute) {
    QDateTime const start = TUtils::fromMinutes(startMinute);
    minute_t free_of_charge = 0;
    minute_t startMinuteDay = start.time().hour()*60 +start.time().minute();
    if (startMinuteDay >= tariff->day_tariff_start &&
        startMinuteDay < tariff->day_tariff_end) {
        free_of_charge = tariff->free_of_charge_day_tariff;
    } else
    if ((startMinuteDay >= tariff->night_tariff_start && startMinuteDay < MINUTES_PER_DAY) ||
        (startMinuteDay >= 0 && startMinuteDay < tariff->night_tariff_end)) {
        free_of_charge = tariff->free_of_charge_night_tariff;
    }
    return free_of_charge;
}

static minute_t computeEffectiveStartMinute(parking_tariff_t const *tariff,
                                            minute_t startMinute) {
    return (startMinute + computeFreeOfCharge(tariff, startMinute));
}

static bool checkCalculatePreConditions(parking_tariff_t const* tariff,
                                        minute_t startMinute,   // CET
                                        minute_t endMinute,     // CET
                                        struct price_t *price) {

    if (startMinute > endMinute) {
        qCritical() << "startMinute > endMinute ("
                    << startMinute << endMinute << ")";
        return false;
    }

    minute_t const parkingTime = endMinute - startMinute;

    if (parkingTime == 0) {
        price->units = 0;
        //qInfo() << "startMinute == endMinute => no parking time";
        return true;
    }

    if (tariff->parking_time_max != -1) {
        if (parkingTime > tariff->parking_time_max) {
            qCritical() << "parkingTime" << parkingTime
                        << ">" << tariff->parking_time_max;
            return false;
        }
    }

    QDateTime const start = TUtils::fromMinutes(startMinute);
    minute_t const effectiveStartMinute = computeEffectiveStartMinute(tariff, startMinute);
    QDateTime const end = TUtils::fromMinutes(endMinute);

    if (effectiveStartMinute > endMinute) {
        //minute_t free_of_charge = computeFreeOfCharge(tariff, startMinute);
        //fprintf(stderr,
        //    "[start:%02d:%02d, ende:%02d:%02d]: %ld free minutes. no charge.\n",
        //    start.time().hour(), start.time().minute(),
        //    end.time().hour(), end.time().minute(), free_of_charge);
        price->units = 0;
        price->valid = true;
        return false;
    }

    // start algorithm
    return true;
}

#define DEBUG_INTERNAL 0
#define DEBUG_MAX_PRICE_AT_MIDNIGHT 0

uint32_t compute_price_for_max_price_at_midnight(parking_tariff_t const *tariff,
                                                 QDateTime const &end,
                                                 TariffStep const *step) {
    uint32_t price_units = 0;
    QDateTime stepDateTime = step->dateTime();
    QDateTime nextStepDateTime;
    QDateTime midnight(stepDateTime.date().addDays(1), QTime(0,0));
    uint32_t day_price_units = 0;
    bool dbg = true;
    while (stepDateTime < end) {
        uint32_t const price_units_tmp = step->price();
        step = step->next();
        nextStepDateTime = step->dateTime();
        if (stepDateTime < midnight) {
            day_price_units += price_units_tmp;
            if (day_price_units > tariff->max_price_at_midnight) {
                day_price_units = tariff->max_price_at_midnight;
                if (dbg) {
#if DEBUG_MAX_PRICE_AT_MIDNIGHT==1
                    fprintf(stderr,
                        "[%s:%02d:%02d price_so_far=%d day_price=%d]\n",
                        stepDateTime.date().toString().toStdString().c_str(),
                        stepDateTime.time().hour(), stepDateTime.time().minute(),
                        price_units, day_price_units);
                    //dbg = false;
#endif
                }
            } else {
#if DEBUG_MAX_PRICE_AT_MIDNIGHT==1
                dbg = true;
                fprintf(stderr,
                    "[%s:%02d:%02d price_so_far=%d day_price=%d]\n",
                    stepDateTime.date().toString().toStdString().c_str(),
                    stepDateTime.time().hour(), stepDateTime.time().minute(),
                    price_units, day_price_units);
#endif
            }
        } else {
            if (day_price_units == tariff->max_price_at_midnight) {
                price_units += day_price_units;
                day_price_units = 0;
            } else {
                day_price_units += price_units_tmp;
            }
#if DEBUG_MAX_PRICE_AT_MIDNIGHT==1
            fprintf(stderr,
                "[%s:%02d:%02d 24h max. price_so_far=%d day_price=%d]\n",
                stepDateTime.date().toString().toStdString().c_str(),
                stepDateTime.time().hour(), stepDateTime.time().minute(),
                price_units, day_price_units);
#endif
            midnight = stepDateTime;
            midnight.setDate(midnight.date().addDays(1));
            midnight.setTime(QTime(0, 0));
        }
        stepDateTime = nextStepDateTime;
    }
    price_units += day_price_units;
    return price_units;
}

TariffStep const *
compute_price_for_parking_ticket_internal(parking_tariff_t const *tariff,
                                          minute_t startMinute, // CET
                                          minute_t endMinute,   // CET
                                          struct price_t *price) {
    TariffStep const *stepDbg = nullptr;

    QDateTime const start = TUtils::fromMinutes(startMinute);
    QDateTime const end = TUtils::fromMinutes(endMinute);

    int const minute = start.time().minute();
    int const hour = start.time().hour();
    int const day = start.date().dayOfWeek()-1;
    int const week = 0;

//    printf("%d%d%04d\n", week, day, hour*60+minute);

    TariffStep const *step = &tariff->m_tariffSteps[week][day][hour*60+minute];

    // TODO: tageslimit muss beruecksichtigt werden
    stepDbg = step;


    uint32_t price_units = 0;
    //if (tariff->max_price_for_24_hours == (uint32_t)(-1)) {
    if (tariff->basic_tariff) {
        while (step->dateTime() < end) {
            price_units += step->price();
            step = step->next();
        }
    }
    if (tariff->max_price_24h_after_arrival){
        QDateTime stepDateTime = step->dateTime();
        QDateTime nextStepDateTime;
        // QDateTime prevStepDateTime;
        QDateTime _24hDateTime = stepDateTime;
        uint32_t _24h_price_units = 0;
        long t0 = _24hDateTime.toSecsSinceEpoch();
        bool dbg = true;
        while (stepDateTime < end) {
            long const t1 = stepDateTime.toSecsSinceEpoch();
            uint32_t const price_units_tmp = step->price();
            step = step->next();
            nextStepDateTime = step->dateTime();
            long const diff = t1 - t0;
            if (diff < SECS_PER_DAY) {
                _24h_price_units += price_units_tmp;
                if (_24h_price_units > tariff->max_price_for_24_hours) {
                    _24h_price_units = tariff->max_price_for_24_hours;
                    if (dbg) {
#if DEBUG_INTERNAL
                        fprintf(stderr,
                            "[%s:%02d:%02d %d]\n",
                            stepDateTime.date().toString().toStdString().c_str(),
                            stepDateTime.time().hour(), stepDateTime.time().minute(),
                            _24h_price_units);
                        dbg = false;
#endif
                    }
                } else {
#if DEBUG_INTERNAL
                    dbg = true;
                    fprintf(stderr,
                        "[%s:%02d:%02d %d]\n",
                        stepDateTime.date().toString().toStdString().c_str(),
                        stepDateTime.time().hour(), stepDateTime.time().minute(),
                        _24h_price_units);
#endif
                }
            } else {
                price_units += _24h_price_units;
                _24h_price_units = price_units_tmp;
#if DEBUG_INTERNAL
                fprintf(stderr,
                    "[%s:%02d:%02d 24h max. %d]\n",
                    stepDateTime.date().toString().toStdString().c_str(),
                    stepDateTime.time().hour(), stepDateTime.time().minute(),
                    price_units);
                fprintf(stderr,
                    "[%s:%02d:%02d %d]\n",
                    stepDateTime.date().toString().toStdString().c_str(),
                    stepDateTime.time().hour(), stepDateTime.time().minute(),
                    _24h_price_units);
#endif

                _24hDateTime = stepDateTime;
                t0 = _24hDateTime.toSecsSinceEpoch();
            }
            // prevStepDateTime = stepDateTime;
            stepDateTime = nextStepDateTime;
        }
        price_units += _24h_price_units;
    }
    if (tariff->max_price_at_midnight) {
        // restart pricing at midnight if current price is max_price_at_midnight
        price_units = compute_price_for_max_price_at_midnight(tariff, end, step);
    }

    price->units = price_units;
    price->netto = roundf((price->units * tariff->unit_scale_factor) / tariff->unit_definition);
    price->vat_percentage = tariff->vat;
    price->vat = price->netto * price->vat_percentage;
    price->brutto = price->netto + price->vat;

    return stepDbg;
}

bool compute_price_for_parking_ticket(parking_tariff_t const *tariff,
                                      minute_t startMinute, // CET
                                      minute_t endMinute,   // CET
                                      struct price_t *price) {
    price->valid = false;
    if (!checkCalculatePreConditions(tariff, startMinute, endMinute, price)) {    // CET
        return price->valid;
    }

    // minute_t const parkingTime = endMinute - startMinute;
    memset(price, 0x00, sizeof(*price));

    minute_t const effectiveStartMinute = computeEffectiveStartMinute(tariff, startMinute);
    // QDateTime const start = TUtils::fromMinutes(startMinute);
    // QDateTime const effStart = TUtils::fromMinutes(effectiveStartMinute);
    // QDateTime const end = TUtils::fromMinutes(endMinute);

    // qDebug() << "XXX" << effStart;
    // qDebug() << "XXX" << end;

    TariffStep const *stepDbg =
        compute_price_for_parking_ticket_internal(tariff,
                                                  effectiveStartMinute,
                                                  endMinute, price);
    if (stepDbg) {
#if 0
        if (tariff->free_of_charge == 0) {
            fprintf(stderr,
                "[EINFAHRT: %s:%02d:%02d, AUSFAHRT: %s:%02d:%02d]\n\t",
                effStart.date().toString().toStdString().c_str(),
                effStart.time().hour(), effStart.time().minute(),
                end.date().toString().toStdString().c_str(),
                end.time().hour(), end.time().minute());
        } else {
            fprintf(stderr,
                "[EINFAHRT: %s:%02d:%02d, %ld Minuten frei -> "
                "EFF. EINFAHRT: %s:%02d:%02d, AUSFAHRT: %s:%02d:%02d]\n\t",
                start.date().toString().toStdString().c_str(),
                start.time().hour(), start.time().minute(),
                tariff->free_of_charge,
                effStart.date().toString().toStdString().c_str(),
                effStart.time().hour(), effStart.time().minute(),
                end.date().toString().toStdString().c_str(),
                end.time().hour(), end.time().minute());
        }
        int cnt = -1;
        while (stepDbg->dateTime() < end) {
            // qDebug() << price_units << step->price();
            fprintf(stderr, "%s %02d:%02d +%u |",
                    (++cnt == 0) ? "|" : "",
                    stepDbg->dateTime().time().hour(),
                    stepDbg->dateTime().time().minute(),
                    stepDbg->price());
            //price_units += stepDbg->price();
            //qDebug() << price_units << step->price();
            stepDbg = stepDbg->next();
        }
#endif

        if (price->units > tariff->max_parking_price_units) {
            qCritical() << "gesamt" << price->units
                        << ">" << tariff->max_parking_price_units;
            return false;
        }

        // fprintf(stderr, " : gesamt %.2f (netto: %.2f)\n", price->brutto/100, price->netto/100);

        return true;

    } else {
        qCritical() << "compute_price_for_parking_ticket";
    }

    return false;
}

#define DEBUG_COMPUTE_DURATION 0

bool compute_duration_for_parking_ticket(parking_tariff_t const *tariff,
                                         minute_t start_parking_time,
                                         minute_t *end_parking_time,
                                         struct price_t const *price) {
    QDateTime const s = TUtils::fromMinutes(start_parking_time);

#if DEBUG_COMPUTE_DURATION==1
    fprintf(stderr, "price %u, start_parking_time %s:%02d:%02d\n",
        price->units, s.date().toString().toStdString().c_str(),
        s.time().hour(), s.time().minute());
#endif

    minute_t free_of_charge = computeFreeOfCharge(tariff, start_parking_time);
    minute_t end_time_at_max_price = start_parking_time;
    for (int now = 0; now < tariff->parking_time_max; ++now) {
        if (price->units == 0) {
            if (free_of_charge >= now) {
                *end_parking_time = start_parking_time + free_of_charge;

#if DEBUG_COMPUTE_DURATION==1
                QDateTime const e = TUtils::fromMinutes(*end_parking_time);
                fprintf(stderr, "price %u, end_parking_time %s:%02d:%02d\n",
                        price->units, e.date().toString().toStdString().c_str(),
                        e.time().hour(), e.time().minute());
#endif

                return true;
            }
        } else {
            minute_t end_time = start_parking_time + now;
            price_t p;
            if (compute_price_for_parking_ticket(tariff, start_parking_time,
                                                 end_time, &p)) {
                if (p.units <= price->units) {
                    end_time_at_max_price = end_time;
                } else {
                    if (end_time_at_max_price >= free_of_charge) {
                        *end_parking_time = end_time_at_max_price - free_of_charge;

#if DEBUG_COMPUTE_DURATION==1
                        QDateTime const e = TUtils::fromMinutes(*end_parking_time);
                        fprintf(stderr, "price %u, end_parking_time %s:%02d:%02d\n",
                            price->units, e.date().toString().toStdString().c_str(),
                            e.time().hour(), e.time().minute());
#endif

                            return true;
                    }
                    fprintf(stderr,
                        "ERROR end_time_at_max_price(%lu) < free_of_charge(%lu)\n",
                        end_time_at_max_price, free_of_charge);
                    break;
                }
            } else {
                // TODO ausgabe
                //return false;
                break;
            }
        }
    }
    fprintf(stderr, "ERROR %s start_parking_time for price %u: %s:%02d:%02d\n",
        __func__, price->units, s.date().toString().toStdString().c_str(),
        s.time().hour(), s.time().minute());
    return false;
}

bool compute_price_for_tages_ticket(parking_tariff_t *tariff,
                                    time_t parking_time_start, // minutes
                                    struct price_t *price) {
// NOT TESTED
    QDateTime dateTime = TUtils::fromMinutes(parking_time_start).addDays(1);
    dateTime.setTime(QTime(0, 0));
    time_t parking_time_end = dateTime.toSecsSinceEpoch() / 60;
    return compute_price_for_parking_ticket(tariff, parking_time_start,
                                            parking_time_end, price);
}

bool compute_price_for_24h_ticket(parking_tariff_t *tariff,
                                  time_t parking_time_start, // minutes
                                  struct price_t *price) {
    QDateTime dateTime = TUtils::fromMinutes(parking_time_start).addDays(1);
    time_t parking_time_end = dateTime.toSecsSinceEpoch() / 60;
// NOT TESTED
    return compute_price_for_parking_ticket(tariff, parking_time_start,
                                            parking_time_end, price);
}

#ifdef __cplusplus
}
#endif
