#include "calculate_parking_tickets_parse_tariff.h"
#include "calculate_parking_tickets_tariff.h"

#include <cstdio>
#include <string>

#include <QDebug>
#include <QDateTime>

#include <rapidjson/filereadstream.h>
#include <rapidjson/document.h>
#include <rapidjson/encodings.h>
#include <rapidjson/error/en.h>
#include "rapidjson/stringbuffer.h"
#include <rapidjson/writer.h>
#include <rapidjson/prettywriter.h>

using namespace rapidjson;

#define MAX_READ_BUFFER_SIZE 10000000

static const char *weekStr[] {
    "week1", "week2", "week3"
};
static const char *weekDayStr[] {
    "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"
};
static const int constexpr STEP_PRICE = 1;
static const int constexpr STEP_NEXT = 2;

parking_tariff_t *parking_tariff_t::parseTariff(const char *confFile) {
    parking_tariff_t *tariff = 0;

    FILE* fp = fopen(confFile, "r");
    if (fp) {
        fseek(fp, 0 , SEEK_END);
        long const bufSize = ftell(fp) + 1024;
        fseek(fp, 0 , SEEK_SET);
        char *readBuffer = (char *)malloc(bufSize);
        if (readBuffer) {
            memset(readBuffer, 0x00, bufSize);

            FileReadStream is(fp, readBuffer, bufSize);
            Document d;

            if (d.ParseStream(is).HasParseError()) {
                fprintf(stderr, "\nError(offset %u): %s\n",
                    (unsigned)d.GetErrorOffset(),
                    GetParseError_En(d.GetParseError()));
                free(readBuffer);
                fclose(fp);
                return 0;
            }

            tariff = new parking_tariff_t();

            if (d.HasMember(FREE_OF_CHARGE)) {
                Value::MemberIterator o = d.FindMember(FREE_OF_CHARGE);
                assert(o != d.MemberEnd());
                assert(o->value.IsString());
                std::string foc = o->value.GetString();
                tariff->free_of_charge = std::stoi(foc);
            }
            if (d.HasMember(UNIT_SCALE_FACTOR)) {
                Value::MemberIterator o = d.FindMember(UNIT_SCALE_FACTOR);
                assert(o != d.MemberEnd());
                assert(o->value.IsString());
                std::string usf = o->value.GetString();
                tariff->unit_scale_factor = std::stof(usf);
            }
            if (d.HasMember(UNIT_DEFINITION)) {
                Value::MemberIterator o = d.FindMember(UNIT_DEFINITION);
                assert(o != d.MemberEnd());
                assert(o->value.IsString());
                std::string udef = o->value.GetString();
                tariff->unit_definition = std::stoi(udef);
            }
            if (d.HasMember(VAT)) {
                Value::MemberIterator o = d.FindMember(VAT);
                assert(o != d.MemberEnd());
                assert(o->value.IsString());
                std::string vat = o->value.GetString();
                tariff->vat = std::stof(vat);
            }
            if (d.HasMember(MAX_PARKING_PRICE)) {
                Value::MemberIterator o = d.FindMember(MAX_PARKING_PRICE);
                assert(o != d.MemberEnd());
                assert(o->value.IsString());
                std::string mp = o->value.GetString();
                if (mp == "unlimited") {
                    tariff->max_parking_price_units = ~0;
                } else {
                    tariff->max_parking_price_units = (uint32_t)std::stoul(mp);
                }
            }
            if (d.HasMember(MAX_PRICE_FOR_24_HOURS)) {
                Value::MemberIterator o = d.FindMember(MAX_PRICE_FOR_24_HOURS);
                assert(o != d.MemberEnd());
                assert(o->value.IsString());
                std::string mp = o->value.GetString();
                if (mp == "unlimited") {
                    tariff->max_price_for_24_hours = ~0;
                } else {
                    tariff->max_price_for_24_hours = (uint32_t)std::stoul(mp);
                }
            }

            QDate today = QDate::currentDate();
            int todayWeekDay = today.dayOfWeek();
            if (todayWeekDay > Qt::Monday) {
                int day = today.day() - todayWeekDay;
                if (!today.setDate(today.year(), today.month(), day)) {
                    qFatal("Setting today failed");
                }
            }

            QDate monday = today;

            // "week1" : {
            for (int w = 0; w < 3; ++w) {
                assert(d.HasMember(weekStr[w]));

                //    "Monday": {
                for (int j = Qt::Monday - 1; j < Qt::Sunday; ++j) {
                    QDateTime startMinute(monday.addDays(w*7+j), QTime( 0, 0));

                    Value::MemberIterator wObj = d.FindMember(weekStr[w]);
                    assert(wObj != d.MemberEnd());
                    assert(wObj->value.IsObject());

                    Value::MemberIterator stepsForDay = wObj->value.FindMember(weekDayStr[j]);
                    assert(stepsForDay != wObj->value.MemberEnd());
                    assert(stepsForDay->value.IsObject());

                    //      name             : time, price, next-tariff-step
                    //      "000000" : [ "00:00", "800000",  "000001" ],
                    //      "000001" : [ "07:00", "350000",  "000002" ],
                    //      ...
                    //      "000014" : [ "20:00", "800000",  "000011" ]
                    //  },
                    int k = 0;
                    for (Value::ConstMemberIterator step = stepsForDay->value.MemberBegin();
                         step != stepsForDay->value.MemberEnd(); ++step) {

                        assert(step->name.IsString());
                        assert(step->value.IsArray());

                        QString name(step->name.GetString());

                        int const week = name.midRef(0, 1).toInt();
                        int const weekDay = name.midRef(1, 1).toInt();
                        int const stepIndex = name.midRef(2).toInt();

                        assert(w == week);
                        assert(j == weekDay);
                        assert(k == stepIndex);

                        uint32_t price_units = QString(step->value[STEP_PRICE].GetString()).toUInt();
                        QString next = QString(step->value[STEP_NEXT].GetString());

                        int const nextWeek = next.midRef(0, 1).toInt();
                        int const nextWeekDay = next.midRef(1, 1).toInt();
                        int const nextStepIndex = next.midRef(2).toInt();

                        //qDebug() << startMinute.addSecs(stepIndex*60)
                        //         << "(" << week << weekDay << stepIndex << ")"
                        //         << "(" << nextWeek << nextWeekDay << nextStepIndex << ")";

                        tariff->m_tariffSteps[week][weekDay][stepIndex] =
                            TariffStep(startMinute.addSecs(stepIndex*60),
                                   price_units,
                                   &tariff->m_tariffSteps[nextWeek][nextWeekDay][nextStepIndex]);

                        ++k;
                    }
                }
            }
            free(readBuffer);
        }
        fclose(fp);
    }

    return tariff;
}
