#pragma once
#include "rocksdb/db.h"
using namespace ROCKSDB_NAMESPACE;

enum Trace{
    WinningTrace,
    PotentialTrace
};

struct ResolutionConfig{
    Trace trace;
    int recall_from;
};

template<typename Poker_t>
std::string get_table_name(int round, const ResolutionConfig& config, const std::string game_name = Poker_t::game_name){
    auto func = [&](const std::string& iso_str, const std::string& dist_str)->std::string {
        return game_name + "_" + iso_str + "_to_" + dist_str + "_distribution_id";
    };

    switch(config.trace) {
        case Trace::WinningTrace: {
            assert(Poker_t::num_rounds > config.recall_from);
            if (round == config.recall_from) {
                return func("nriso_" + std::to_string(round + 1), "winning");
            } else {
                return func(
                    "priso_" + std::to_string(round + 1) + "_from_" + std::to_string(config.recall_from + 1),
                    "winning_trace"
                );
            }
            break;
        }
        case Trace::PotentialTrace: {
            // auto& potential_trace = config.data.potential_trace;
            assert(Poker_t::num_rounds- 1 > config.recall_from);
            if (round == config.recall_from) {
                return func("nriso_" + std::to_string(round + 1), "winning");
            } else {
                return func(
                    "priso_" + std::to_string(round + 1) + "_from_" + std::to_string(config.recall_from + 1),
                    "potential_trace"
                );
            }
            break;
        }
    }
}

template<typename Poker_t>
class FullResolutionAbstr {

    std::string tables_name[Poker_t::num_rounds];
    std::size_t recall_froms[Poker_t::num_rounds];
    std::string db_game_name;
public:
    FullResolutionAbstr(const std::vector<ResolutionConfig> &configs, std::string db_game_name = Poker_t::game_name):db_game_name(db_game_name) {
        assert(Poker_t::num_rounds == configs.size());
        for(int r = 0; r < Poker_t::num_rounds; ++r) {
            recall_froms[r] = configs[r].recall_from;
            tables_name[r] = get_table_name<Poker_t>(r, configs[r], db_game_name);
        }

    }

    void operator()(int recall_from[Poker_t::num_rounds], uint64_t iso_size_round[Poker_t::num_rounds], uint64_t bucket_size_round[Poker_t::num_rounds], uint64_t* street_bucket[Poker_t::num_rounds]) const {
        std::copy(std::begin(recall_froms), std::end(recall_froms), recall_from);

        for(int r = 0; r < Poker_t::num_rounds; ++r){
            iso_size_round[r] = Hand<Poker_t>::get_isomorphism_size(r, recall_froms[r]);
            street_bucket[r] = new uint64_t[iso_size_round[r]];
        }

        rocksdb::DB* db;
        rocksdb::DBOptions options;
        options.create_if_missing = false; // do not create database if it does not exist

        // open DB with two column families
        std::vector<ColumnFamilyDescriptor> column_families;
        // open the new one, too
        for(const std::string& table_name : tables_name) {
            column_families.push_back(ColumnFamilyDescriptor(
                table_name, ColumnFamilyOptions()));
        }
        // have to open default column family
        column_families.push_back(ColumnFamilyDescriptor(
            kDefaultColumnFamilyName, ColumnFamilyOptions()));
        std::vector<ColumnFamilyHandle*> handles;
        Status s = DB::OpenForReadOnly(DBOptions(), "data/full_resolution_abstraction/" + db_game_name, column_families, &handles, &db);
        assert(s.ok());

        rocksdb::ReadOptions read_options;
        bool need_bswap;
        for (int r = 0; r < Poker_t::num_rounds; ++r) {
            rocksdb::Iterator* it = db->NewIterator(read_options, handles[r]);
            {// 判断要不要换端
                it->SeekToFirst();
                it->Next();
                rocksdb::Slice key1 = it->key();
                uint32_t key1_uint32 = *(reinterpret_cast<const uint32_t*>(key1.data()));
                need_bswap = key1_uint32==1? false : true;
                assert((!need_bswap && key1_uint32==1)||
                       (need_bswap && __builtin_bswap32(key1_uint32)==1));
            }

            //最后一位是bucket_size
            it->SeekToLast();
            rocksdb::Slice last_key = it->key();
            rocksdb::Slice last_value = it->value();
            uint32_t iso_size = *(reinterpret_cast<const uint32_t*>(last_key.data()));
            uint32_t bucket_size = *(reinterpret_cast<const uint32_t*>(last_value.data()));
            if (need_bswap) {
                iso_size = __builtin_bswap32(iso_size);
                bucket_size = __builtin_bswap32(bucket_size);
            }
            bucket_size_round[r] = bucket_size;

            uint32_t key_uint32, value_uint32;
            for (it->Prev(); it->Valid(); it->Prev()) {
                rocksdb::Slice key = it->key();
                rocksdb::Slice value = it->value();

                key_uint32 = *(reinterpret_cast<const uint32_t*>(key.data()));
                value_uint32 = *(reinterpret_cast<const uint32_t*>(value.data()));
                if (need_bswap) {
                    key_uint32 = __builtin_bswap32(key_uint32);
                    value_uint32 = __builtin_bswap32(value_uint32);
                }
                street_bucket[r][key_uint32] = value_uint32;
            }
        }

        for (auto handle : handles) {
            db->DestroyColumnFamilyHandle(handle);
        }
        // Close the database
        delete db;
    }
};
