#define RAPIDJSON_HAS_STDSTRING 1
#include <rapidjson/filewritestream.h>
#include <rapidjson/writer.h>
#include "data_internal.hxx"

#define KEY(x) writer.Key(#x, sizeof(#x)-1)

using Writer = rapidjson::Writer<rapidjson::FileWriteStream>;

void save_node(Writer &writer, Node *node) {
    writer.StartObject();

    KEY(id); writer.Uint64(node->id);
    KEY(name); writer.String(node->name);
    KEY(file); writer.Bool(node->file);
    KEY(preview); writer.Bool(node->preview);
    KEY(size); writer.Uint64(node->size);
    KEY(parent); writer.Uint64(node->parent == nullptr ? 0 : node->parent->id);

    KEY(children);
    writer.StartArray();
    for (const auto &child : node->children)
        writer.Uint64(child->id);
    writer.EndArray();

    writer.EndObject();
}

void save_user(Writer &writer, User *user) {
    std::shared_lock lock{user->node_lock};
    writer.StartObject();

    KEY(id); writer.Uint64(user->id);
    KEY(name); writer.String(user->name);
    KEY(password); writer.String(user->password);
    KEY(tfa_secret); writer.String(user->tfa_secret);
    KEY(enabled); writer.Bool(user->enabled);
    KEY(admin); writer.Bool(user->admin);
    KEY(tfa_enabled); writer.Bool(user->tfa_enabled);
    KEY(tfa_mail); writer.Bool(user->tfa_mail);
    KEY(next_node_id); writer.Uint64(user->next_node_id);

    KEY(nodes);
    writer.StartArray();
    for (const auto &node : user->nodes)
        save_node(writer, node.second.get());
    writer.EndArray();

    writer.EndObject();
}

char save_buf[65536];
void save(Data* data) {
    data_logger->info("Saving data");
    try {
        {
            FileWrapper f{data_new_file.c_str(), "w"};
            rapidjson::FileWriteStream stream{f.f, save_buf, sizeof(save_buf)};
            Writer writer{stream};

            std::shared_lock lock{data->user_lock};

            writer.StartObject();

            KEY(version); writer.Uint64(data->version);
            KEY(next_user_id); writer.Uint64(data->next_user_id);

            KEY(users);
            writer.StartArray();
            for (const auto &user : data->users)
                save_user(writer, user.second.get());
            writer.EndArray();

            writer.EndObject();
        }
        data_logger->info("Finished writing data");
        if (std::filesystem::exists(data_cur_file))
            std::filesystem::copy_file(data_cur_file, data_old_file, std::filesystem::copy_options::overwrite_existing);
        std::filesystem::rename(data_new_file, data_cur_file);
        data_logger->info("Save done");
    } catch (std::exception &e) {
        data_logger->error("Error while saving: {}, retrying...", e.what());
        data->save_flag.test_and_set();
    }
}

void save_worker(Data *data) {
    while (!data->shutdown_flag.test()) {
        data->save_flag.wait(false);
        do {
            data->save_flag.clear();
            std::this_thread::sleep_for(std::chrono::seconds{2});
        } while (data->save_flag.test());
        save(data);
    }
    data_logger->info("Data saver stopping");
    save(data);
}

void Data::start_save_thread() {
    save();
    shutdown_flag.clear();
    this->save_thread = std::thread{save_worker, this};
}

void Data::save() {
    save_flag.test_and_set();
    save_flag.notify_all();
}
