#pragma once

#include "template/domain_independent/abstraction.h"
#include <tlx/thread_pool.hpp>
#include "template/algorithm/iteration/iteration_util.hpp"
#include <list>
#include <queue>

template<typename Poker>
class PrAbstrHits{
public:
    using Poker_t = Poker;
private:
    // double deal_probability_given_last_prabstr;
    uint64_t prbucket_size_round[Poker_t::num_rounds]{0};
    uint64_t* priso_to_prbucket_round[Poker_t::num_rounds]{nullptr};
    std::list<uint64_t>* prbucket_last_to_current_round[Poker_t::num_rounds]{nullptr};
    uint64_t* prbucket_current_to_last_round[Poker_t::num_rounds]{nullptr};
    // uint64_t* prbucket_to_oribucket_round[Poker_t::num_rounds]{nullptr};
    int64_t* last_hits_round[Poker_t::num_rounds]{nullptr};
    int64_t* current_hits_round[Poker_t::num_rounds]{nullptr};

    uint64_t addbucket_size_round[Poker_t::num_rounds]{0};
    uint64_t* priso_to_addbucket_round[Poker_t::num_rounds]{nullptr};
    std::list<uint64_t>* addbucket_decompose_to_prbuckets_round[Poker_t::num_rounds]{nullptr};
    std::vector<uint64_t>* addbucket_partition_by_threads_round[Poker_t::num_rounds]{nullptr};

private:
    void clear_();

public:

    ~PrAbstrHits();

    void perfect_recall_config(const Abstraction<Poker>& abstr);

    void original_config(const Abstraction<Poker>& abstr);

    uint64_t abstract_view(const type::card_t* hole, const type::card_t* board, int round) const;

    uint64_t abstract_view(const Hand<Poker_t>& hand) const;

    uint64_t abstract_view(uint64_t priso, int round) const;

    uint64_t addori_abstract_view(const type::card_t* hole, const type::card_t* board, int round) const;

    uint64_t addori_abstract_view(const Hand<Poker_t>& hand) const;

    uint64_t addori_abstract_view(uint64_t priso, int round) const;

    uint64_t round_prbucket_size(int round) const;

    uint64_t round_addbucket_size(int round) const;

    int64_t current_prbucket_hits(uint64_t prbucket, int round) const;

    int64_t last_prbucket_hits(uint64_t prbucket, int round) const;

    uint64_t prbucket_current_to_last(uint64_t prbucket, int round) const;

    const std::vector<uint64_t>& addbucket_partitionvec_by_threads(int thread, int round) const;

    const std::list<uint64_t>& addbucket_decompose_to_prbuckets(uint64_t addbucket, int round) const;

    // const std::list<std::pair<uint64_t, int64_t>>& last_hits_given_bucket_round(uint64_t bucket, int round) const;

    // const std::list<uint64_t>& bucket_to_next_round(uint64_t prbucket, int round) const;

    void print(FILE *stream) const;
};

template<typename Poker>
void PrAbstrHits<Poker>::clear_(){

    for (int r = 0; r < Poker_t::num_rounds; ++r){
        delete[] priso_to_prbucket_round[r];
        priso_to_prbucket_round[r] = nullptr;

        delete[] prbucket_last_to_current_round[r];
        prbucket_last_to_current_round[r] = nullptr;

        delete[] prbucket_current_to_last_round[r];
        prbucket_current_to_last_round[r] = nullptr;

        delete[] last_hits_round[r];
        last_hits_round[r] = nullptr;

        delete[] current_hits_round[r];
        current_hits_round[r] = nullptr;

        if (addbucket_size_round[r] > 0) {
            delete[] priso_to_addbucket_round[r];
            priso_to_addbucket_round[r] = nullptr;

            delete[] addbucket_decompose_to_prbuckets_round[r];
            addbucket_decompose_to_prbuckets_round[r] = nullptr;

            delete[] addbucket_partition_by_threads_round[r];
            addbucket_partition_by_threads_round[r] = nullptr;
        }
    }

    std::fill(prbucket_size_round, prbucket_size_round + Poker_t::num_rounds, 0);
    std::fill(addbucket_size_round, addbucket_size_round + Poker_t::num_rounds, 0);
}

template<typename Poker>
void PrAbstrHits<Poker>::perfect_recall_config(const Abstraction<Poker>& abstr){

    type::card_t hand_card[Poker_t::hand_len[Poker_t::num_rounds-1]];
    for(int r = 0; r < Poker_t::num_rounds; ++r){
        uint64_t last_prbucket_size = r == 0? 1 : prbucket_size_round[r-1];
        uint64_t priso_size = Hand<Poker_t>::get_isomorphism_size(r, 0);


        /// 一次priso循环:
        /// 1. 统计prbucket size round
        /// 2. 构造priso to prbucket [temp!]
        /// 3. 当前round视角下的last_prbucket -> prbucket [temp!]
        /// 4. 构造一个temp maps: original bucket -> [last_prbucket:(prbucket [temp!], hit count)]
        prbucket_size_round[r] = 0; //计数该轮次有多少桶
        priso_to_prbucket_round[r] = new uint64_t [priso_size]; std::fill(priso_to_prbucket_round[r], priso_to_prbucket_round[r]+priso_size, static_cast<uint64_t>(-1));
        prbucket_last_to_current_round[r] = new std::list<uint64_t>[last_prbucket_size];
        type::hash_table<uint64_t, std::pair<uint64_t, std::atomic_int64_t>> *oribucket_mapto_last_prbuckethits = new type::hash_table<uint64_t, std::pair<uint64_t, std::atomic_int64_t>>[abstr.round_bucket_size(r)];
        for (uint64_t priso = 0; priso < priso_size; ++priso) {
            Hand<Poker_t>::hand_unisomorphism(priso, r, 0, hand_card);
            uint64_t current_original_bucket = abstr.abstract_view(priso, r);
            uint64_t last_prbucket = r == 0? 0 : this->abstract_view(hand_card, hand_card + Poker_t::hole_len[r], r-1);

            if (auto it = oribucket_mapto_last_prbuckethits[current_original_bucket].find(last_prbucket); 
                it == oribucket_mapto_last_prbuckethits[current_original_bucket].end()){
                uint64_t prbucket_temp= prbucket_size_round[r]++;
                priso_to_prbucket_round[r][priso] = prbucket_temp;
                prbucket_last_to_current_round[r][last_prbucket].push_back(prbucket_temp);
                oribucket_mapto_last_prbuckethits[current_original_bucket].insert(std::make_pair(last_prbucket, std::make_pair(prbucket_temp, 0)));
            }
            else {
                priso_to_prbucket_round[r][priso] = it->second.first;
            }
        }

        std::list<uint64_t> original_bucket_seq;
        addbucket_size_round[r] = 0;
        priso_to_addbucket_round[r] = nullptr;
        addbucket_partition_by_threads_round[r] = nullptr;
        original_bucket_seq.insert(original_bucket_seq.end(), abstr.round_bucket_size(r), 0);
        std::iota(original_bucket_seq.begin(), original_bucket_seq.end(), 0);

        // {original bucket -> [last_prbucket:(prbucket [temp!], hit count)]} => {original bucket -> [last_prbucket:(prbucket, hit count)]}
        uint64_t *prbuckettemp_to_prbucket = new uint64_t[prbucket_size_round[r]];
        uint64_t prbucket_cnt = 0;
        for (uint64_t ob: original_bucket_seq){
            for (auto &[last_bucket, bucket_hits_pair] : oribucket_mapto_last_prbuckethits[ob]){
                uint64_t prbucket = prbucket_cnt++;
                prbuckettemp_to_prbucket[bucket_hits_pair.first] = prbucket;
                bucket_hits_pair.first = prbucket;
            }
        }
        assert(prbucket_cnt == prbucket_size_round[r]);
        // priso to prbucket [temp!] => priso to prbucket
        for (uint64_t i = 0; i < priso_size; ++i) {
            priso_to_prbucket_round[r][i] = prbuckettemp_to_prbucket[priso_to_prbucket_round[r][i]];
        }
        // {last_prbucket -> prbucket [temp!]} => {last_prbucket -> prbucket}; prbucket -> last_prbucket
        prbucket_current_to_last_round[r] = new uint64_t[prbucket_size_round[r]]; std::fill(prbucket_current_to_last_round[r], prbucket_current_to_last_round[r] + prbucket_size_round[r], static_cast<uint64_t>(-1));
        for (uint64_t last_prbucket = 0; last_prbucket < last_prbucket_size; ++last_prbucket) {
            for (uint64_t& prbucket_temp_r : prbucket_last_to_current_round[r][last_prbucket]) {
                prbucket_temp_r = prbuckettemp_to_prbucket[prbucket_temp_r];
                prbucket_current_to_last_round[r][prbucket_temp_r] = last_prbucket;
            }
        }
        assert(std::none_of(prbucket_current_to_last_round[r], prbucket_current_to_last_round[r]+prbucket_size_round[r], [](uint64_t i){ return i == static_cast<uint64_t>(-1);}));

        addbucket_decompose_to_prbuckets_round[r] = nullptr;
        delete[] prbuckettemp_to_prbucket;

        std::atomic_int64_t *last_prbucket_hits = new std::atomic_int64_t[last_prbucket_size];
        std::fill(last_prbucket_hits, last_prbucket_hits + last_prbucket_size, 0);

        std::shared_ptr<tlx::ThreadPool> sp_pool = std::make_shared<tlx::ThreadPool>(overall_define::num_threads);
        iteration::multithread_dealing_upto_current_round_hand<Poker_t>(sp_pool, [ this
                                                                                 , &abstr
                                                                                 , r
                                                                                 , oribucket_mapto_last_prbuckethits
                                                                                 , last_prbucket_hits](type::card_t* hole, type::card_t* board){
            uint64_t current_original_bucket = abstr.abstract_view(hole, board, r);
            uint64_t last_prbucket = r == 0? 0 : this->abstract_view(hole, board, r-1);

            ++oribucket_mapto_last_prbuckethits[current_original_bucket].at(last_prbucket).second;
            ++last_prbucket_hits[last_prbucket];
        }, r);
        sp_pool->loop_until_empty();

        last_hits_round[r] = new int64_t [last_prbucket_size];
        std::copy(last_prbucket_hits, last_prbucket_hits + last_prbucket_size, last_hits_round[r]);
        // prbucket_to_oribucket_round[r] = new uint64_t[prbucket_size_round[r]]; std::fill(prbucket_to_oribucket_round[r], prbucket_to_oribucket_round[r] + prbucket_size_round[r], -1);
        current_hits_round[r] = new int64_t [prbucket_size_round[r]]; std::fill(current_hits_round[r], current_hits_round[r] + prbucket_size_round[r], 0);
        for (uint64_t ob = 0, original_bucket_size = abstr.round_bucket_size(r); ob < original_bucket_size; ++ob){
            for (auto const &[last_prbucket, bucket_hits_pair] : oribucket_mapto_last_prbuckethits[ob]){
                current_hits_round[r][bucket_hits_pair.first] += bucket_hits_pair.second;
                // prbucket_to_oribucket_round[r][bucket_hits_pair.first] = ob;
            }
        }

        assert(std::none_of(current_hits_round[r], current_hits_round[r]+prbucket_size_round[r], [](int64_t i){ return i == 0;}));
    
        delete[] last_prbucket_hits;
        delete[] oribucket_mapto_last_prbuckethits;
    }
}

template<typename Poker>
void PrAbstrHits<Poker>::original_config(const Abstraction<Poker>& abstr){
    // 验证perfect_recall性质
    bool perfect_recall = true;
    type::card_t hand_card[Poker_t::hand_len[Poker_t::num_rounds-1]];
    for(int r = 0; r < Poker_t::num_rounds; ++r){
        uint64_t last_prbucket_size = r == 0? 1 : prbucket_size_round[r-1];
        uint64_t priso_size = Hand<Poker_t>::get_isomorphism_size(r, 0);

        /// 一次priso循环:
        /// 1. 统计prbucket size round
        /// 2. 构造priso to prbucket [temp!]
        /// 3. 当前round视角下的last_prbucket -> prbucket [temp!]
        /// 4. 构造一个temp maps: original bucket -> [last_prbucket:(prbucket [temp!], hit count)]
        prbucket_size_round[r] = 0; //计数该轮次有多少桶
        priso_to_prbucket_round[r] = new uint64_t [priso_size]; std::fill(priso_to_prbucket_round[r], priso_to_prbucket_round[r]+priso_size, static_cast<uint64_t>(-1));
        prbucket_last_to_current_round[r] = new std::list<uint64_t>[last_prbucket_size];
        type::hash_table<uint64_t, std::pair<uint64_t, std::atomic_int64_t>> *oribucket_mapto_last_prbuckethits = new type::hash_table<uint64_t, std::pair<uint64_t, std::atomic_int64_t>>[abstr.round_bucket_size(r)];
        for (uint64_t priso = 0; priso < priso_size; ++priso) {
            Hand<Poker_t>::hand_unisomorphism(priso, r, 0, hand_card);
            uint64_t current_original_bucket = abstr.abstract_view(priso, r);
            uint64_t last_prbucket = r == 0? 0 : this->abstract_view(hand_card, hand_card + Poker_t::hole_len[r], r-1);

            if (auto it = oribucket_mapto_last_prbuckethits[current_original_bucket].find(last_prbucket); 
                it == oribucket_mapto_last_prbuckethits[current_original_bucket].end()){
                uint64_t prbucket_temp= prbucket_size_round[r]++;
                priso_to_prbucket_round[r][priso] = prbucket_temp;
                prbucket_last_to_current_round[r][last_prbucket].push_back(prbucket_temp);
                oribucket_mapto_last_prbuckethits[current_original_bucket].insert(std::make_pair(last_prbucket, std::make_pair(prbucket_temp, 0)));
            }
            else {
                priso_to_prbucket_round[r][priso] = it->second.first;
            }
        }

        // 如果本轮次统计出的prbucket size与original bucket size相同说明此时原抽象还是perfect recall
        if (abstr.round_bucket_size(r) != prbucket_size_round[r]) {
            perfect_recall = false;
        }

        // 提前准备一个original_abstr的向量
        std::list<uint64_t> original_bucket_seq;

        if (perfect_recall) {
            addbucket_size_round[r] = 0;
            priso_to_addbucket_round[r] = nullptr;
            addbucket_partition_by_threads_round[r] = nullptr;
            original_bucket_seq.insert(original_bucket_seq.end(), abstr.round_bucket_size(r), 0);
            std::iota(original_bucket_seq.begin(), original_bucket_seq.end(), 0);
        }
        else {
            addbucket_size_round[r] = abstr.round_bucket_size(r);
            std::list<uint64_t>* addbucket_partition_by_threads_through_list = new std::list<uint64_t>[overall_define::num_threads];

            std::vector<int> partition_count(overall_define::num_threads, 0);
            auto comp = [&partition_count](const int& lth, const int& rth) -> bool {
                if (partition_count[lth] > partition_count[rth])
                    return true;
                else if (partition_count[rth] > partition_count[lth])
                    return false;
                // partition_count[lth] == partition_count[rth]
                return lth > rth;
            }; // 定义greater比较，会使得优先队列中权重越小的，序号越小的在前面
            std::vector<int> thread_idx(overall_define::num_threads); std::iota(thread_idx.begin(), thread_idx.end(), 0);
            std::priority_queue<int , std::vector<int>, decltype(comp)> priority_partition(comp, thread_idx);

            // 从大到小的方式构造一个带权addbucket vector
            std::vector<int> sorted_addbucket(addbucket_size_round[r], 0); std::iota(sorted_addbucket.begin(), sorted_addbucket.end(), 0);
            std::sort(sorted_addbucket.begin(), sorted_addbucket.end(), [oribucket_mapto_last_prbuckethits](const auto& lhs, const auto& rhs) -> bool{
                if (std::size_t lsz = oribucket_mapto_last_prbuckethits[lhs].size(), rsz = oribucket_mapto_last_prbuckethits[rhs].size(); lsz > rsz)
                    return true;
                else if(rsz > lsz)
                    return false;
                // rsz == lsz
                return lhs < rhs;
            });
            for (const int addbucket_idx : sorted_addbucket) {
                int th = priority_partition.top();
                priority_partition.pop();
                partition_count[th] += oribucket_mapto_last_prbuckethits[addbucket_idx].size() + 1; //+1是因为本身original bucket也要进行传递，要算入成本
                addbucket_partition_by_threads_through_list[th].push_back(addbucket_idx);
                priority_partition.push(th);
            }
            
            addbucket_partition_by_threads_round[r] = new std::vector<uint64_t>[overall_define::num_threads];
            //把各个划分按照原序从小到大排序，填入original_abstr向量
            for (int th = 0; th < overall_define::num_threads; ++th){
                addbucket_partition_by_threads_through_list[th].sort();
                original_bucket_seq.insert(original_bucket_seq.end(), addbucket_partition_by_threads_through_list[th].begin(), addbucket_partition_by_threads_through_list[th].end());

                addbucket_partition_by_threads_round[r][th].reserve(addbucket_partition_by_threads_through_list[th].size());
                addbucket_partition_by_threads_round[r][th].insert(addbucket_partition_by_threads_round[r][th].end(), addbucket_partition_by_threads_through_list[th].begin(), addbucket_partition_by_threads_through_list[th].end());
            }

            delete[] addbucket_partition_by_threads_through_list;

            priso_to_addbucket_round[r] = new uint64_t[priso_size];
            for (uint64_t priso = 0; priso < priso_size; ++priso) {
                Hand<Poker_t>::hand_unisomorphism(priso, r, 0, hand_card);
                uint64_t current_original_bucket = abstr.abstract_view(priso, r);
                priso_to_addbucket_round[r][priso] = current_original_bucket;
            }
        }

        // {original bucket -> [last_prbucket:(prbucket [temp!], hit count)]} => {original bucket -> [last_prbucket:(prbucket, hit count)]}
        uint64_t *prbuckettemp_to_prbucket = new uint64_t[prbucket_size_round[r]];
        uint64_t prbucket_cnt = 0;
        for (uint64_t ob: original_bucket_seq){
            for (auto &[last_bucket, bucket_hits_pair] : oribucket_mapto_last_prbuckethits[ob]){
                uint64_t prbucket = prbucket_cnt++;
                prbuckettemp_to_prbucket[bucket_hits_pair.first] = prbucket;
                bucket_hits_pair.first = prbucket;
            }
        }
        assert(prbucket_cnt == prbucket_size_round[r]);
        // priso to prbucket [temp!] => priso to prbucket
        for (uint64_t i = 0; i < priso_size; ++i) {
            priso_to_prbucket_round[r][i] = prbuckettemp_to_prbucket[priso_to_prbucket_round[r][i]];
        }
        // {last_prbucket -> prbucket [temp!]} => {last_prbucket -> prbucket}; prbucket -> last_prbucket
        prbucket_current_to_last_round[r] = new uint64_t[prbucket_size_round[r]]; std::fill(prbucket_current_to_last_round[r], prbucket_current_to_last_round[r] + prbucket_size_round[r], static_cast<uint64_t>(-1));
        for (uint64_t last_prbucket = 0; last_prbucket < last_prbucket_size; ++last_prbucket) {
            for (uint64_t& prbucket_temp_r : prbucket_last_to_current_round[r][last_prbucket]) {
                prbucket_temp_r = prbuckettemp_to_prbucket[prbucket_temp_r];
                prbucket_current_to_last_round[r][prbucket_temp_r] = last_prbucket;
            }
        }
        assert(std::none_of(prbucket_current_to_last_round[r], prbucket_current_to_last_round[r]+prbucket_size_round[r], [](uint64_t i){ return i == static_cast<uint64_t>(-1);}));

        if (perfect_recall){
            addbucket_decompose_to_prbuckets_round[r] = nullptr;
        }
        else{
            addbucket_decompose_to_prbuckets_round[r] = new std::list<uint64_t>[abstr.round_bucket_size(r)];
            for (uint64_t ob: original_bucket_seq){
                for (auto &[last_bucket, bucket_hits_pair] : oribucket_mapto_last_prbuckethits[ob]){
                    addbucket_decompose_to_prbuckets_round[r][ob].push_back(bucket_hits_pair.first);
                }
            }
        }

        delete[] prbuckettemp_to_prbucket;

        std::atomic_int64_t *last_prbucket_hits = new std::atomic_int64_t[last_prbucket_size];
        std::fill(last_prbucket_hits, last_prbucket_hits + last_prbucket_size, 0);

        std::shared_ptr<tlx::ThreadPool> sp_pool = std::make_shared<tlx::ThreadPool>(overall_define::num_threads);
        iteration::multithread_dealing_upto_current_round_hand<Poker_t>(sp_pool, [ this
                                                                                 , &abstr
                                                                                 , r
                                                                                 , oribucket_mapto_last_prbuckethits
                                                                                 , last_prbucket_hits](type::card_t* hole, type::card_t* board){
            uint64_t current_original_bucket = abstr.abstract_view(hole, board, r);
            uint64_t last_prbucket = r == 0? 0 : this->abstract_view(hole, board, r-1);

            ++oribucket_mapto_last_prbuckethits[current_original_bucket].at(last_prbucket).second;
            ++last_prbucket_hits[last_prbucket];
        }, r);
        sp_pool->loop_until_empty();

        last_hits_round[r] = new int64_t [last_prbucket_size];
        std::copy(last_prbucket_hits, last_prbucket_hits + last_prbucket_size, last_hits_round[r]);
        // prbucket_to_oribucket_round[r] = new uint64_t[prbucket_size_round[r]]; std::fill(prbucket_to_oribucket_round[r], prbucket_to_oribucket_round[r] + prbucket_size_round[r], -1);
        current_hits_round[r] = new int64_t [prbucket_size_round[r]]; std::fill(current_hits_round[r], current_hits_round[r] + prbucket_size_round[r], 0);
        for (uint64_t ob = 0, original_bucket_size = abstr.round_bucket_size(r); ob < original_bucket_size; ++ob){
            for (auto const &[last_prbucket, bucket_hits_pair] : oribucket_mapto_last_prbuckethits[ob]){
                current_hits_round[r][bucket_hits_pair.first] += bucket_hits_pair.second;
                // prbucket_to_oribucket_round[r][bucket_hits_pair.first] = ob;
            }
        }

        assert(std::none_of(current_hits_round[r], current_hits_round[r]+prbucket_size_round[r], [](int64_t i){ return i == 0;}));
    
        delete[] last_prbucket_hits;
        delete[] oribucket_mapto_last_prbuckethits;
    }

}

template<typename Poker>
PrAbstrHits<Poker>::~PrAbstrHits() {
    clear_();
}

template<typename Poker>
uint64_t PrAbstrHits<Poker>::abstract_view(const type::card_t* hole, const type::card_t* board, int round) const{
    Hand<Poker> hand(hole, board, round);
    return priso_to_prbucket_round[round][hand.get_hand_isomorphism(0)];
}

template<typename Poker>
uint64_t PrAbstrHits<Poker>::abstract_view(const Hand<Poker>& hand) const {
    return priso_to_prbucket_round[hand.get_round()][hand.get_hand_isomorphism(0)];
}

template<typename Poker>
uint64_t PrAbstrHits<Poker>::abstract_view(uint64_t priso, int round) const {
    return priso_to_prbucket_round[round][priso];
}

template<typename Poker>
uint64_t PrAbstrHits<Poker>::addori_abstract_view(const type::card_t* hole, const type::card_t* board, int round) const{
    Hand<Poker> hand(hole, board, round);
    return priso_to_addbucket_round[round][hand.get_hand_isomorphism(0)];
}

template<typename Poker>
uint64_t PrAbstrHits<Poker>::addori_abstract_view(const Hand<Poker>& hand) const {
    return priso_to_addbucket_round[hand.get_round()][hand.get_hand_isomorphism(0)];
}

template<typename Poker>
uint64_t PrAbstrHits<Poker>::addori_abstract_view(uint64_t priso, int round) const {
    return priso_to_addbucket_round[round][priso];
}

template<typename Poker>
uint64_t PrAbstrHits<Poker>::round_prbucket_size(int round) const {
    return prbucket_size_round[round];
}

template<typename Poker>
uint64_t PrAbstrHits<Poker>::round_addbucket_size(int round) const {
    return addbucket_size_round[round];
}

template<typename Poker>
int64_t PrAbstrHits<Poker>::current_prbucket_hits(uint64_t bucket, int round) const {
    return current_hits_round[round][bucket];
}

template<typename Poker>
int64_t PrAbstrHits<Poker>::last_prbucket_hits(uint64_t last_bucket, int round) const {
    return last_hits_round[round][last_bucket];
}

// template<typename Poker>
// const std::list<std::pair<uint64_t, int64_t>>& PrAbstrHits<Poker>::last_hits_given_bucket_round(uint64_t bucket, int round) const {
//     return bucket_to_hits_given_last[round][bucket];
// }

// template<typename Poker>
// const std::list<uint64_t>& PrAbstrHits<Poker>::bucket_to_next_round(uint64_t bucket, int round) const {
//     return last_bucket_to_develop[round+1][bucket]; // bucket := last_bucket
// }

template<typename Poker>
void PrAbstrHits<Poker>::print(FILE *stream) const {

    fprintf(stream, "Game: %s\n", Poker_t::game_name.data());

    for (int r = 0; r < Poker_t::num_rounds; ++r){
        fprintf(stream, "Round: %d\t ; Isomorphism size: %ld\t; PrBucket size: %ld\n", r, Hand<Poker_t>::get_isomorphism_size(r, 0), prbucket_size_round[r]);
        
        fprintf(stream, "priso: prbucket\n");
        for(uint64_t priso = 0, priso_size = Hand<Poker_t>::get_isomorphism_size(r, 0); priso < priso_size; ++priso) {
            uint64_t prbucket = abstract_view(priso, r);
            fprintf(stream, "%9ld: %9ld\n", priso, prbucket);
        }

        fprintf(stream, "[prbucket: hits]\t[last_prbucket: hits]\n");
        for(uint64_t prbucket = 0; prbucket < prbucket_size_round[r]; ++prbucket) {
            uint64_t last_prbucket = prbucket_current_to_last_round[r][prbucket];
            fprintf(stream, "[%9ld:%9ld]\t[%9ld:%9ld]\n", prbucket
                                                        , current_hits_round[r][prbucket]
                                                        , prbucket_current_to_last_round[r][prbucket]
                                                        , last_hits_round[r][last_prbucket]);

        }

        if (addbucket_size_round[r] > 0) {

            fprintf(stream, "priso: addbucket\n");
            for(uint64_t priso = 0, priso_size = Hand<Poker_t>::get_isomorphism_size(r, 0); priso < priso_size; ++priso) {
                uint64_t addbucket = addori_abstract_view(priso, r);
                fprintf(stream, "%9ld: %9ld\n", priso, addbucket);
            }

            fprintf(stream, "addbucket: \t[prbucket: hits]...\n");
            for(uint64_t addbucket = 0; addbucket < addbucket_size_round[r]; ++addbucket) {
                fprintf(stream, "%9ld: ", addbucket);
                for (const auto prbucket : addbucket_decompose_to_prbuckets_round[r][addbucket]){
                    fprintf(stream, "[%9ld: %9ld], ", prbucket, current_hits_round[r][prbucket]);
                }
                fprintf(stream, "\n");

            }
        }
    }
}

template<typename Poker>
uint64_t PrAbstrHits<Poker>::prbucket_current_to_last(uint64_t prbucket, int round) const {
    return prbucket_current_to_last_round[round][prbucket];
}

template<typename Poker>
const std::list<uint64_t>& PrAbstrHits<Poker>::addbucket_decompose_to_prbuckets(uint64_t addbucket, int round) const {
    return addbucket_decompose_to_prbuckets_round[round][addbucket];
}

template<typename Poker>
const std::vector<uint64_t>& PrAbstrHits<Poker>::addbucket_partitionvec_by_threads(int thread, int round) const {
    return addbucket_partition_by_threads_round[round][thread];
}
