
#pragma once

#include <cstdint>
#include <string>
#include <vector>
#include <functional>
#include <memory>
#include <sstream>
#include <fstream>
#include <tlx/thread_pool.hpp>
#include <boost/log/trivial.hpp>
#include <set>

#include "overall_define.h"
#include "template/domain_independent/showdown_histogram.h"

// potential是考虑下轮次所有手牌组合
// showdown的情况是考虑当前hand发展到最后一轮负胜平的分布

template<typename Poker>
class AbstractionLimit{
public:
    using Poker_t = Poker;

    AbstractionLimit(){
        sp_pool = std::make_shared<tlx::ThreadPool>(overall_define::num_threads);
    }

    void run();

    template<int round>
    void recall_early_game();

    template<int round>
    void explore_late_game();

    void wait_necessary();

    std::string result() const ;

    void write(const char* dir) const ;

public:
// protected:
    std::shared_ptr<tlx::ThreadPool> sp_pool;

    std::vector<ShowdownHistogram> showdown_histograms[Poker_t::num_rounds];
    std::vector<int64_t> showdown_histogram_counter[Poker_t::num_rounds];// 存储每个最后圈牌胜直方的个数
    type::hash_table<ShowdownHistogram, uint64_t, ContainerHash<ShowdownHistogram>> showdown_histogram_2_idx[Poker_t::num_rounds]; // map from showdown_hostogram to idx of 'showdown_histograms'

    std::vector<std::vector<uint64_t>> potential_histograms[Poker_t::num_rounds-1];
    std::vector<int64_t> potential_histogram_counter[Poker_t::num_rounds-1];
    type::hash_table<std::vector<uint64_t>, uint64_t, ContainerHash<std::vector<uint64_t>>> potential_histogram_2_idx[Poker_t::num_rounds-1];

    std::vector<uint64_t> bu_showdown_coordinate[Poker_t::num_rounds]; // 每轮的size是isomorphism
    std::vector<uint64_t> bu_potential_coordinate[Poker_t::num_rounds-1];

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    std::vector<std::vector<uint64_t>> showdown_perfectrecall_features[Poker_t::num_rounds];
    std::vector<int64_t> showdown_perfectrecall_feature_counter[Poker_t::num_rounds];
    type::hash_table<std::vector<uint64_t>, uint64_t, ContainerHash<std::vector<uint64_t>>> showdown_perfectrecall_feature_2_idx[Poker_t::num_rounds];

    std::vector<std::vector<uint64_t>> potential_perfectrecall_features[Poker_t::num_rounds];
    std::vector<int64_t> potential_perfectrecall_feature_counter[Poker_t::num_rounds];
    type::hash_table<std::vector<uint64_t>, uint64_t, ContainerHash<std::vector<uint64_t>>> potential_perfectrecall_feature_2_idx[Poker_t::num_rounds];

    std::vector<uint64_t> bub_showdown_coordinate[Poker_t::num_rounds]; // 每轮的size是isomorphism
    std::vector<uint64_t> bub_potential_coordinate[Poker_t::num_rounds];
};

template<typename Poker>
void AbstractionLimit<Poker>::run(){
    recall_early_game<Poker_t::num_rounds-1>();
    wait_necessary();
    explore_late_game<0>();
}

template<typename Poker>
void AbstractionLimit<Poker>::wait_necessary(){
    sp_pool->loop_until_empty();
}

template<typename Poker>
std::string AbstractionLimit<Poker>::result() const {
    std::stringstream sstrm;
    sstrm <<"{\n"; {

        sstrm << "\t\"game\": \"" << Poker_t::game_name << "\",\n";

        sstrm << "\t\"isomorphism\": {\n"; {
            for(int i = 0; i<Poker_t::num_rounds; ++i){
                sstrm << "\t\t\"street " << i+1 << "\": perfect "<<Hand<Poker_t>::get_isomorphism_size(i, 0)<< ", imperfect "<< Hand<Poker_t>::get_isomorphism_size(i, i)<<(i<Poker_t::num_rounds-1?",\n":"\n");
            }
        }
        sstrm << "\t},\n";

        sstrm << "\t\"bottom-up\": {\n"; {
            for(int i = 0; i < Poker_t::num_rounds; ++i){
                sstrm << "\t\t\"street " << i+1 << "\": {\n";{
                    if(i!=Poker_t::num_rounds-1)
                        sstrm <<"\t\t\t\"potential\":"<< potential_histogram_counter[i].size()<<",\n";
                    sstrm <<"\t\t\t\"showdown\":"<< showdown_histogram_counter[i].size()<<"\n";
                }
                sstrm << "\t\t}"<<(i<Poker_t::num_rounds-1?",\n":"\n");
            }
        }
        sstrm << "\t},\n";

        sstrm << "\t\"bottom-up-bottom\": {\n"; {
            for(int i = 0; i < Poker_t::num_rounds; ++i){
                sstrm << "\t\t\"street " << i+1 << "\": {\n";{
                    sstrm <<"\t\t\t\"potential\":"<< potential_perfectrecall_feature_counter[i].size()<<",\n";
                    sstrm <<"\t\t\t\"showdown\":"<< showdown_perfectrecall_feature_counter[i].size()<<"\n";
                }
                sstrm << "\t\t}"<<(i<Poker_t::num_rounds-1?",\n":"\n");
            }
        }
        sstrm << "\t}\n";
    }
    sstrm << "}\n";

    return std::string(sstrm.str());
}

template<typename Poker>
template<int round>
void AbstractionLimit<Poker>::recall_early_game(){

    if constexpr(round < 0)
        return;
    
    // else{
    //     BOOST_LOG_TRIVIAL(debug) << "recall_early_game round: "<< round;

    //     auto start_t = std::chrono::high_resolution_clock::now();

    //     if constexpr (round >= Poker_t::num_rounds-1) {
    //         static_assert(round == Poker_t::num_rounds-1);
    //         // 最后一街，算胜率

    //         std::vector<type::hash_table<ShowdownHistogram, std::vector<uint64_t>, ContainerHash<ShowdownHistogram>>> showdown_histogram_maps(overall_define::num_threads);

    //         showdown_histograms[round].clear();

    //         // 每个线程遍历一个小区间
    //         auto compute_histogram_func = [&showdown_histogram_maps](int thread_idx, uint64_t step_begin, uint64_t step_end){

    //             type::card_t hand_card[Poker_t::hand_len[round]];
    //             ShowdownHistogram showdown_histogram;

    //             for(uint64_t i = step_begin; i < step_end; ++i){
    //                 Hand<Poker_t>::hand_unindex(i, round, hand_card); // 拿到isomorphism i对应的牌

    //                 showdown_histogram = Evaluator<Poker_t>::evaluate_potential(hand_card);

    //                 if(showdown_histogram_maps[thread_idx].contains(showdown_histogram)){
    //                     showdown_histogram_maps[thread_idx].at(showdown_histogram).push_back(i);
    //                 }
    //                 else{
    //                     showdown_histogram_maps[thread_idx].insert({showdown_histogram, {i}});// index, counter
    //                 }
    //             }

    //             BOOST_LOG_TRIVIAL(debug) << "showdown_histogram_maps "<< thread_idx<< " size: "<< showdown_histogram_maps[thread_idx].size();
    //         };

    //         const uint64_t isomorphism_size = Poker_t::num_hand_isomorphism_round[round];
    //         const uint64_t step = (isomorphism_size + overall_define::num_threads - 1) / overall_define::num_threads;
    //         for (uint64_t real_step_begin = 0; real_step_begin < isomorphism_size; real_step_begin += step) {

    //             int th = real_step_begin/step;
    //             uint64_t real_step_end = (real_step_begin + step > isomorphism_size) ? isomorphism_size : real_step_begin + step;
    //             auto bind_fun = std::bind(compute_histogram_func, th, real_step_begin, real_step_end);

    //             sp_pool->enqueue(bind_fun);
    //         }

    //         // 线程等待每个区间
    //         wait_necessary();

    //         // 将各个线程汇总,由于是按照线程编号积累，如果hash_table的iterator是按照插入顺序遍历那么与开一个线程的顺序一样（这个假设有可能不成立）
    //         showdown_histogram_counter[round].clear();
    //         showdown_histogram_2_idx[round].clear();
    //         showdown_histograms[round].clear();
    //         for(uint64_t th = 0, idx = 0; th < overall_define::num_threads; ++th){

    //             for(const auto& [showdown_histogram, count] : showdown_histogram_maps[th]){
    //                 if(showdown_histogram_2_idx[round].contains(showdown_histogram)){
    //                     showdown_histogram_counter[round][showdown_histogram_2_idx[round].at(showdown_histogram)] += count.size();
    //                 }
    //                 else {
    //                     showdown_histograms[round].push_back(showdown_histogram); //注册新的直方图
    //                     const auto& [_, res] = showdown_histogram_2_idx[round].insert({showdown_histogram, idx}); // 对直方图生成唯一坐标
    //                     assert(res == true);
    //                     showdown_histogram_counter[round].push_back(count.size()); // 统计直方图个数
    //                     ++idx;
    //                 }
    //             }
    //         }
    //         assert(showdown_histogram_counter[round].size() == showdown_histograms[round].size());
    //         assert(showdown_histogram_2_idx[round].size() == showdown_histograms[round].size());
    //         BOOST_LOG_TRIVIAL(debug) << "showdown_histogram size: "<<showdown_histograms[round].size();

    //         // 将该轮次每个isomorphism 映射到 showdown histogram的idx
    //         bu_showdown_coordinate[round].clear();
    //         bu_showdown_coordinate[round].resize(Poker_t::num_hand_isomorphism_round[round]);
    //         std::fill(bu_showdown_coordinate[round].begin(), bu_showdown_coordinate[round].end(), -1);
    //         for (int th = 0; th < overall_define::num_threads; ++th){
    //             sp_pool->enqueue([&bu_showdown_coordinate = bu_showdown_coordinate[round], &showdown_histogram_maps, &showdown_histogram_2_idx = showdown_histogram_2_idx[round], th]() {
    //                 for (const auto& [showdown_histogram, counters] : showdown_histogram_maps[th]){
    //                     auto showdown_histogram_idx = showdown_histogram_2_idx.at(showdown_histogram);
    //                     for (auto isomorphism : counters){
    //                         bu_showdown_coordinate[isomorphism] = showdown_histogram_idx;
    //                     }
    //                 }
    //             });
    //         }

    //         // 线程等待每个区间
    //         wait_necessary();
    //     }
    //     else /* round = 0, ..., Poker_t::num_rounds-2 */{
            
    //         // 构造直方图到集合的一个映射，用vector表示集合是因为我们不需要按值存取，且能保证唯一性
    //         std::vector< type::hash_table< std::vector<uint64_t>, std::vector<uint64_t>, ContainerHash<std::vector<uint64_t>>>> potential_histogram_maps(overall_define::num_threads);
    //         std::vector< type::hash_table< ShowdownHistogram, std::vector<uint64_t>, ContainerHash<ShowdownHistogram>>> showdown_histogram_maps(overall_define::num_threads);
    //         potential_histograms[round].clear();
    //         showdown_histograms[round].clear();

    //         // 每个线程遍历一个小区间
    //         auto compute_histogram_func = [&potential_histogram_maps, &showdown_histogram_maps, this](int thread_idx, uint64_t step_begin, uint64_t step_end) {
    //             constexpr int deal_card_len = Poker_t::deal_hole_round[round+1] + Poker_t::deal_board_round[round+1]; //下轮要发多少牌
    //             constexpr int except_board_deck_size = Poker_t::deck_len-Poker_t::hand_len[round]; // 对于玩家而言抛去可见的牌后deck还剩多少牌

    //             type::card_t hand_card[Poker_t::hand_len[round + 1]];
    //             type::card_t *hole_card = hand_card;
    //             type::card_t *board_card = hand_card + Poker_t::hole_len[round + 1]; // 发了下轮牌后board的起始位置
    
    //             type::card_t fake_new_cards[deal_card_len]; // 用来假分牌对齐位置
    //             std::fill(fake_new_cards, fake_new_cards + deal_card_len, -1);

    //             for(uint64_t i = step_begin; i < step_end; ++i){
    //                 // 针对同构构造可能的手牌
    //                 Hand<Poker_t>::hand_unindex(i, round, hand_card);

    //                 // 把本轮次手牌与board按照下轮次的牌位置分开（方便发下轮牌）
    //                 add_deal<Poker_t>(hand_card, fake_new_cards, round+1);

    //                 // 获得该手牌情况下deck的余牌
    //                 std::vector<type::card_t> except_board_deck;
    //                 except_board_deck.reserve(except_board_deck_size);
    //                 std::remove_copy_if(Poker_t::deck.begin(), Poker_t::deck.end(), std::inserter(except_board_deck, except_board_deck.begin()),
    //                     [&hand_card = std::as_const(hand_card)](type::card_t val){
    //                         auto eq = [&val](type::card_t v){
    //                             return val == v;
    //                         };
    //                         return std::any_of(hand_card, hand_card + Poker_t::hand_len[round], eq);
    //                     });
    //                 assert(except_board_deck.size() == except_board_deck_size);

    //                 // 获取potential histogram
    //                 std::vector<uint64_t> potential_histogram;
    //                 potential_histogram.reserve(Poker_t::single_deal_combine_num_round[round]);
    //                 combinatorics::for_multi_combinations(
    //                     [ hole_card
    //                     , board_card
    //                     , &hand_card
    //                     , &potential_histogram
    //                     , &bu_next_coordinate = round == Poker_t::num_rounds-2? bu_showdown_coordinate[round+1] : bu_potential_coordinate[round+1]
    //                     ] (std::vector<type::card_t>::iterator card_begin, std::vector<type::card_t>::iterator card_end){ // vector<type::card_t>::iterator
    //                         assert(card_begin + Poker_t::deal_hole_round[round+1] + Poker_t::deal_board_round[round+1] == card_end);
                            
    //                         // 该轮次发私有牌
    //                         if constexpr ( Poker_t::deal_hole_round[round+1] > 0 )
    //                             std::copy(card_begin, card_begin + Poker_t::deal_hole_round[round+1], hole_card + Poker_t::hole_len[round]);
                            
    //                         //该轮次发公共牌
    //                         if constexpr ( Poker_t::deal_board_round[round+1] > 0 )
    //                             std::copy(card_begin + Poker_t::deal_hole_round[round+1], card_end, board_card + Poker_t::board_len[round]);
                    
    //                         uint64_t next_round_isomorphism = Hand<Poker_t>::compute_isomorphism(round+1, hand_card);
    //                         potential_histogram.push_back(bu_next_coordinate[next_round_isomorphism]);

    //                     }
    //                     , except_board_deck.begin(), except_board_deck.end(), std::span<const type::card_t>{&Poker_t::single_deal_combine[Poker_t::single_deal_combine_begin_round[round+1]], &Poker_t::single_deal_combine[Poker_t::single_deal_combine_end_round[round+1]]}
    //                 );
    //                 assert(potential_histogram.size() == Poker_t::single_deal_combine_num_round[round+1]);
    //                 std::sort(potential_histogram.begin(), potential_histogram.end()); //排序之后消除槽位置的影响
    //                 if(potential_histogram_maps[thread_idx].contains(potential_histogram)){
    //                     potential_histogram_maps[thread_idx].at(potential_histogram).push_back(i);
    //                 }
    //                 else{
    //                     potential_histogram_maps[thread_idx].insert({potential_histogram, {i}});// index, counter
    //                 }

    //                 // 获取showdown histogram
    //                 ShowdownHistogram showdown_histogram{0};
    //                 for(uint64_t next_round_isomorphism : potential_histogram) {
                        
    //                     int next_showdown_coordinate = bu_showdown_coordinate[round+1][next_round_isomorphism];
    //                     showdown_histogram += showdown_histograms[round+1][next_showdown_coordinate];
    //                 }
    //                 if(showdown_histogram_maps[thread_idx].contains(showdown_histogram)){
    //                     showdown_histogram_maps[thread_idx].at(showdown_histogram).push_back(i);
    //                 }
    //                 else{
    //                     showdown_histogram_maps[thread_idx].insert({showdown_histogram, {i}});// index, counter
    //                 }
    //             }

    //             BOOST_LOG_TRIVIAL(debug) << "potential_histogram_maps "<< thread_idx<< " size: "<< potential_histogram_maps[thread_idx].size();
    //             BOOST_LOG_TRIVIAL(debug) << "showdown_histogram_maps "<< thread_idx<< " size: "<< showdown_histogram_maps[thread_idx].size();
    //         };

    //         const uint64_t isomorphism_size = Poker_t::num_hand_isomorphism_round[round];
    //         const uint64_t step = (isomorphism_size + overall_define::num_threads - 1) / overall_define::num_threads;
    //         for (uint64_t real_step_begin = 0; real_step_begin < isomorphism_size; real_step_begin += step) {

    //             int th = real_step_begin/step;
    //             uint64_t real_step_end = (real_step_begin + step > isomorphism_size) ? isomorphism_size : real_step_begin + step;
    //             auto bind_fun = std::bind(compute_histogram_func, th, real_step_begin, real_step_end);

    //             sp_pool->enqueue(bind_fun);
    //         }

    //         // 线程等待每个区间
    //         wait_necessary();

    //         // 将各个线程汇总,由于是按照线程编号积累，如果hash_table的iterator是按照插入顺序遍历那么与开一个线程的顺序一样（这个假设有可能不成立）
    //         potential_histogram_counter[round].clear();
    //         potential_histogram_2_idx[round].clear();
    //         potential_histograms[round].clear();
    //         for(uint64_t th = 0, idx = 0; th < overall_define::num_threads; ++th){

    //             for(const auto& [potential_histogram, count] : potential_histogram_maps[th]) {
    //                 if(potential_histogram_2_idx[round].contains(potential_histogram)){
    //                     potential_histogram_counter[round][potential_histogram_2_idx[round].at(potential_histogram)] += count.size();
    //                 }
    //                 else {
    //                     potential_histograms[round].push_back(potential_histogram); //注册新的直方图
    //                     const auto& [_, res] = potential_histogram_2_idx[round].insert({potential_histogram, idx}); // 对直方图生成唯一坐标
    //                     assert(res == true);
    //                     potential_histogram_counter[round].push_back(count.size()); // 统计直方图个数
    //                     ++idx;
    //                 }
    //             }
    //         }
    //         assert(potential_histogram_counter[round].size() == potential_histograms[round].size());
    //         assert(potential_histogram_2_idx[round].size() == potential_histograms[round].size());
    //         BOOST_LOG_TRIVIAL(debug) << "potential_histogram size: "<<potential_histograms[round].size();

    //         showdown_histogram_counter[round].clear();
    //         showdown_histogram_2_idx[round].clear();
    //         showdown_histograms[round].clear();
    //         for(uint64_t th = 0, idx = 0; th < overall_define::num_threads; ++th){

    //             for(const auto& [showdown_histogram, count] : showdown_histogram_maps[th]) {
    //                 if(showdown_histogram_2_idx[round].contains(showdown_histogram)){
    //                     showdown_histogram_counter[round][showdown_histogram_2_idx[round].at(showdown_histogram)] += count.size();
    //                 }
    //                 else {
    //                     showdown_histograms[round].push_back(showdown_histogram); //注册新的直方图
    //                     const auto& [_, res] = showdown_histogram_2_idx[round].insert({showdown_histogram, idx}); // 对直方图生成唯一坐标
    //                     assert(res == true);
    //                     showdown_histogram_counter[round].push_back(count.size()); // 统计直方图个数
    //                     ++idx;
    //                 }
    //             }
    //         }
    //         assert(showdown_histogram_counter[round].size() == showdown_histograms[round].size());
    //         assert(showdown_histogram_2_idx[round].size() == showdown_histograms[round].size());
    //         BOOST_LOG_TRIVIAL(debug) << "showdown_histogram size: "<<showdown_histograms[round].size();

    //         // 将该轮次每个isomorphism 映射到 showdown/potential histogram的idx
    //         bu_potential_coordinate[round].clear();
    //         bu_potential_coordinate[round].resize(Poker_t::num_hand_isomorphism_round[round]);
    //         std::fill(bu_potential_coordinate[round].begin(), bu_potential_coordinate[round].end(), -1);
    //         bu_showdown_coordinate[round].clear();
    //         bu_showdown_coordinate[round].resize(Poker_t::num_hand_isomorphism_round[round]);
    //         std::fill(bu_showdown_coordinate[round].begin(), bu_showdown_coordinate[round].end(), -1);
    //         for (int th = 0; th < overall_define::num_threads; ++th){
    //             sp_pool->enqueue([ &bu_potential_coordinate = bu_potential_coordinate[round]
    //                              , &potential_histogram_maps
    //                              , &potential_histogram_2_idx = potential_histogram_2_idx[round]
    //                              , &bu_showdown_coordinate = bu_showdown_coordinate[round]
    //                              , &showdown_histogram_maps
    //                              , &showdown_histogram_2_idx = showdown_histogram_2_idx[round]
    //                              , th]() {
    //                 for (const auto& [potential_histogram, counters] : potential_histogram_maps[th]){
    //                     auto potential_histogram_idx = potential_histogram_2_idx.at(potential_histogram);
    //                     for (auto isomorphism : counters){
    //                         bu_potential_coordinate[isomorphism] = potential_histogram_idx;
    //                     }
    //                 }

    //                 for (const auto& [showdown_histogram, counters] : showdown_histogram_maps[th]){
    //                     auto showdown_histogram_idx = showdown_histogram_2_idx.at(showdown_histogram);
    //                     for (auto isomorphism : counters){
    //                         bu_showdown_coordinate[isomorphism] = showdown_histogram_idx;
    //                     }
    //                 }
    //             });
    //         }

    //         // 线程等待每个区间
    //         wait_necessary();
    //     }

    //     auto end_t = std::chrono::high_resolution_clock::now();
    //     int duration_t = std::chrono::duration_cast<std::chrono::milliseconds>(end_t - start_t).count();
    //     printf("recall_early_game round %d cost %d ms\n", round, duration_t);

    //     recall_early_game<round-1>();
    // }
    else {
        BOOST_LOG_TRIVIAL(debug) << "recall_early_game round: "<< round;

        auto start_t = std::chrono::high_resolution_clock::now();

        if constexpr (round >= Poker_t::num_rounds-1) {
            static_assert(round == Poker_t::num_rounds-1);
            // 最后一街，算胜率

            std::vector<type::hash_table<ShowdownHistogram, std::vector<uint64_t>, ContainerHash<ShowdownHistogram>>> showdown_histogram_maps(overall_define::num_threads);

            showdown_histograms[round].clear();

            // 每个线程遍历一个小区间
            auto compute_histogram_func = [&showdown_histogram_maps](int thread_idx, uint64_t step_begin, uint64_t step_end){

                type::card_t hand_card[Poker_t::hand_len[round]];
                ShowdownHistogram showdown_histogram;

                for(uint64_t i = step_begin; i < step_end; ++i){
                    Hand<Poker_t>::hand_unisomorphism(i, round, round, hand_card); // 拿到isomorphism i对应的牌

                    showdown_histogram = Evaluator<Poker_t>::evaluate_potential(hand_card);

                    if(showdown_histogram_maps[thread_idx].contains(showdown_histogram)){
                        showdown_histogram_maps[thread_idx].at(showdown_histogram).push_back(i);
                    }
                    else{
                        showdown_histogram_maps[thread_idx].insert({showdown_histogram, {i}});// index, counter
                    }
                }

                BOOST_LOG_TRIVIAL(debug) << "showdown_histogram_maps "<< thread_idx<< " size: "<< showdown_histogram_maps[thread_idx].size();
            };

            const uint64_t isomorphism_size = Hand<Poker_t>::get_isomorphism_size(round, round);
            const uint64_t step = (isomorphism_size + overall_define::num_threads - 1) / overall_define::num_threads;
            for (uint64_t real_step_begin = 0; real_step_begin < isomorphism_size; real_step_begin += step) {

                int th = real_step_begin/step;
                uint64_t real_step_end = (real_step_begin + step > isomorphism_size) ? isomorphism_size : real_step_begin + step;
                auto bind_fun = std::bind(compute_histogram_func, th, real_step_begin, real_step_end);

                sp_pool->enqueue(bind_fun);
            }

            // 线程等待每个区间
            wait_necessary();

            // 将各个线程汇总,由于是按照线程编号积累，如果hash_table的iterator是按照插入顺序遍历那么与开一个线程的顺序一样（这个假设有可能不成立）
            std::set<ShowdownHistogram> showdown_histograms_set;
            for(uint64_t th = 0; th < overall_define::num_threads; ++th){
                for(const auto& [showdown_histogram, count] : showdown_histogram_maps[th]){
                    showdown_histograms_set.insert(showdown_histogram);
                }
            }

            showdown_histograms[round].clear();showdown_histograms[round].reserve(showdown_histograms_set.size());
            showdown_histogram_2_idx[round].clear();
            showdown_histogram_counter[round].resize(showdown_histograms_set.size(), 0);

            for(const auto& showdown_histogram: showdown_histograms_set){
                showdown_histograms[round].push_back(showdown_histogram);
                showdown_histogram_2_idx[round].insert({showdown_histogram, showdown_histograms[round].size()-1});
            }

            for(uint64_t th = 0; th < overall_define::num_threads; ++th){
                for(const auto& [showdown_histogram, count] : showdown_histogram_maps[th]){
                    showdown_histogram_counter[round][showdown_histogram_2_idx[round].at(showdown_histogram)] += count.size();
                }
            }
            assert(showdown_histogram_counter[round].size() == showdown_histograms[round].size());
            assert(showdown_histogram_2_idx[round].size() == showdown_histograms[round].size());
            BOOST_LOG_TRIVIAL(debug) << "showdown_histogram size: "<<showdown_histograms[round].size();

            // 将该轮次每个isomorphism 映射到 showdown histogram的idx
            bu_showdown_coordinate[round].clear();
            bu_showdown_coordinate[round].resize(Hand<Poker_t>::get_isomorphism_size(round, round));
            std::fill(bu_showdown_coordinate[round].begin(), bu_showdown_coordinate[round].end(), -1);
            for (int th = 0; th < overall_define::num_threads; ++th){
                sp_pool->enqueue([&bu_showdown_coordinate = bu_showdown_coordinate[round], &showdown_histogram_maps, &showdown_histogram_2_idx = showdown_histogram_2_idx[round], th]() {
                    for (const auto& [showdown_histogram, counters] : showdown_histogram_maps[th]){
                        auto showdown_histogram_idx = showdown_histogram_2_idx.at(showdown_histogram);
                        for (auto isomorphism : counters){
                            bu_showdown_coordinate[isomorphism] = showdown_histogram_idx;
                        }
                    }
                });
            }

            // 线程等待每个区间
            wait_necessary();
        }
        else /* round = 0, ..., Poker_t::num_rounds-2 */{
            
            // 构造直方图到集合的一个映射，用vector表示集合是因为我们不需要按值存取，且能保证唯一性
            std::vector< type::hash_table< std::vector<uint64_t>, std::vector<uint64_t>, ContainerHash<std::vector<uint64_t>>>> potential_histogram_maps(overall_define::num_threads);
            std::vector< type::hash_table< ShowdownHistogram, std::vector<uint64_t>, ContainerHash<ShowdownHistogram>>> showdown_histogram_maps(overall_define::num_threads);
            potential_histograms[round].clear();
            showdown_histograms[round].clear();

            // 每个线程遍历一个小区间
            auto compute_histogram_func = [&potential_histogram_maps, &showdown_histogram_maps, this](int thread_idx, uint64_t step_begin, uint64_t step_end) {
                constexpr int deal_card_len = Poker_t::deal_hole_round[round+1] + Poker_t::deal_board_round[round+1]; //下轮要发多少牌
                constexpr int except_board_deck_size = Poker_t::deck_len-Poker_t::hand_len[round]; // 对于玩家而言抛去可见的牌后deck还剩多少牌

                type::card_t hand_card[Poker_t::hand_len[round + 1]];
                type::card_t *hole_card = hand_card;
                type::card_t *board_card = hand_card + Poker_t::hole_len[round + 1]; // 发了下轮牌后board的起始位置
    
                type::card_t fake_new_cards[deal_card_len]; // 用来假分牌对齐位置
                std::fill(fake_new_cards, fake_new_cards + deal_card_len, -1);

                for(uint64_t i = step_begin; i < step_end; ++i){
                    // 针对同构构造可能的手牌
                    Hand<Poker_t>::hand_unisomorphism(i, round, round, hand_card);

                    // 获得该手牌情况下deck的余牌
                    std::vector<type::card_t> except_board_deck;
                    except_board_deck.reserve(except_board_deck_size);
                    std::remove_copy_if(Poker_t::deck.begin(), Poker_t::deck.end(), std::inserter(except_board_deck, except_board_deck.begin()),
                        [&hand_card = std::as_const(hand_card)](type::card_t val){
                            auto eq = [&val](type::card_t v){
                                return val == v;
                            };
                            return std::any_of(hand_card, hand_card + Poker_t::hand_len[round], eq);
                        });
                    assert(except_board_deck.size() == except_board_deck_size);

                    // 把本轮次手牌与board按照下轮次的牌位置分开（方便发下轮牌）
                    add_deal<Poker_t>(hand_card, fake_new_cards, round+1);

                    // 获取potential histogram 与showdown distribution
                    std::vector<uint64_t> potential_histogram;
                    potential_histogram.reserve(Poker_t::single_deal_combine_num_round[round]);
                    ShowdownHistogram showdown_histogram{0};
                    combinatorics::for_multi_combinations(
                        [ hole_card
                        , board_card
                        , &hand_card
                        , &potential_histogram
                        , &showdown_histogram
                        , &bu_next_coordinate = round == Poker_t::num_rounds-2? bu_showdown_coordinate[round+1] : bu_potential_coordinate[round+1]
                        , this
                        ] (std::vector<type::card_t>::iterator card_begin, std::vector<type::card_t>::iterator card_end){ // vector<type::card_t>::iterator
                            assert(card_begin + Poker_t::deal_hole_round[round+1] + Poker_t::deal_board_round[round+1] == card_end);
                            
                            // 该轮次发私有牌
                            if constexpr ( Poker_t::deal_hole_round[round+1] > 0 )
                                std::copy(card_begin, card_begin + Poker_t::deal_hole_round[round+1], hole_card + Poker_t::hole_len[round]);
                            
                            //该轮次发公共牌
                            if constexpr ( Poker_t::deal_board_round[round+1] > 0 )
                                std::copy(card_begin + Poker_t::deal_hole_round[round+1], card_end, board_card + Poker_t::board_len[round]);
                    
                            uint64_t next_round_isomorphism = Hand<Poker_t>::compute_hand_isomorphism(round+1, round+1, hand_card);
                            potential_histogram.push_back(bu_next_coordinate[next_round_isomorphism]);

                            int next_showdown_coordinate = this->bu_showdown_coordinate[round+1][next_round_isomorphism];
                            showdown_histogram += this->showdown_histograms[round+1][next_showdown_coordinate];
                        }
                        , except_board_deck.begin(), except_board_deck.end(), std::span<const type::card_t>{&Poker_t::single_deal_combine[Poker_t::single_deal_combine_begin_round[round+1]], &Poker_t::single_deal_combine[Poker_t::single_deal_combine_end_round[round+1]]}
                    );
                    assert(potential_histogram.size() == Poker_t::single_deal_combine_num_round[round+1]);

                    std::sort(potential_histogram.begin(), potential_histogram.end()); //排序之后消除槽位置的影响
                    if(potential_histogram_maps[thread_idx].contains(potential_histogram)){
                        potential_histogram_maps[thread_idx].at(potential_histogram).push_back(i);
                    }
                    else{
                        potential_histogram_maps[thread_idx].insert({potential_histogram, {i}});// index, counter
                    }

                    // 获取showdown histogram
                    if(showdown_histogram_maps[thread_idx].contains(showdown_histogram)){
                        showdown_histogram_maps[thread_idx].at(showdown_histogram).push_back(i);
                    }
                    else{
                        showdown_histogram_maps[thread_idx].insert({showdown_histogram, {i}});// index, counter
                    }
                }

                BOOST_LOG_TRIVIAL(debug) << "potential_histogram_maps "<< thread_idx<< " size: "<< potential_histogram_maps[thread_idx].size();
                BOOST_LOG_TRIVIAL(debug) << "showdown_histogram_maps "<< thread_idx<< " size: "<< showdown_histogram_maps[thread_idx].size();
            };

            const uint64_t isomorphism_size = Hand<Poker_t>::get_isomorphism_size(round, round);
            const uint64_t step = (isomorphism_size + overall_define::num_threads - 1) / overall_define::num_threads;
            for (uint64_t real_step_begin = 0; real_step_begin < isomorphism_size; real_step_begin += step) {

                int th = real_step_begin/step;
                uint64_t real_step_end = (real_step_begin + step > isomorphism_size) ? isomorphism_size : real_step_begin + step;
                auto bind_fun = std::bind(compute_histogram_func, th, real_step_begin, real_step_end);

                sp_pool->enqueue(bind_fun);
            }

            // 线程等待每个区间
            wait_necessary();

            // 将各个线程汇总,由于是按照线程编号积累，如果hash_table的iterator是按照插入顺序遍历那么与开一个线程的顺序一样（这个假设有可能不成立）
            // potential_histogram_counter[round].clear();
            // potential_histogram_2_idx[round].clear();
            // potential_histograms[round].clear();
            // for(uint64_t th = 0, idx = 0; th < overall_define::num_threads; ++th){

            //     for(const auto& [potential_histogram, count] : potential_histogram_maps[th]) {
            //         if(potential_histogram_2_idx[round].contains(potential_histogram)){
            //             potential_histogram_counter[round][potential_histogram_2_idx[round].at(potential_histogram)] += count.size();
            //         }
            //         else {
            //             potential_histograms[round].push_back(potential_histogram); //注册新的直方图
            //             const auto& [_, res] = potential_histogram_2_idx[round].insert({potential_histogram, idx}); // 对直方图生成唯一坐标
            //             assert(res == true);
            //             potential_histogram_counter[round].push_back(count.size()); // 统计直方图个数
            //             ++idx;
            //         }
            //     }
            // }
            std::set<std::vector<uint64_t>> potential_histograms_set;
            for(uint64_t th = 0; th < overall_define::num_threads; ++th){
                for(const auto& [potential_histogram, count] : potential_histogram_maps[th]){
                    potential_histograms_set.insert(potential_histogram);
                }
            }

            potential_histograms[round].clear();potential_histograms[round].reserve(potential_histograms_set.size());
            potential_histogram_2_idx[round].clear();
            potential_histogram_counter[round].clear(); potential_histogram_counter[round].resize(potential_histograms_set.size(), 0);

            for(const auto& potential_histogram: potential_histograms_set){
                potential_histograms[round].push_back(potential_histogram);
                potential_histogram_2_idx[round].insert({potential_histogram, potential_histograms[round].size()-1});
            }

            for(uint64_t th = 0; th < overall_define::num_threads; ++th){
                for(const auto& [potential_histogram, count] : potential_histogram_maps[th]){
                    potential_histogram_counter[round][potential_histogram_2_idx[round].at(potential_histogram)] += count.size();
                }
            }
            assert(potential_histogram_counter[round].size() == potential_histograms[round].size());
            assert(potential_histogram_2_idx[round].size() == potential_histograms[round].size());
            BOOST_LOG_TRIVIAL(debug) << "potential_histogram size: "<<potential_histograms[round].size();

            std::set<ShowdownHistogram> showdown_histograms_set;
            for(uint64_t th = 0; th < overall_define::num_threads; ++th){
                for(const auto& [showdown_histogram, count] : showdown_histogram_maps[th]){
                    showdown_histograms_set.insert(showdown_histogram);
                }
            }

            showdown_histograms[round].clear();showdown_histograms[round].reserve(showdown_histograms_set.size());
            showdown_histogram_2_idx[round].clear();
            showdown_histogram_counter[round].clear(); showdown_histogram_counter[round].resize(showdown_histograms_set.size(), 0);

            for(const auto& showdown_histogram: showdown_histograms_set){
                showdown_histograms[round].push_back(showdown_histogram);
                showdown_histogram_2_idx[round].insert({showdown_histogram, showdown_histograms[round].size()-1});
            }

            for(uint64_t th = 0; th < overall_define::num_threads; ++th){
                for(const auto& [showdown_histogram, count] : showdown_histogram_maps[th]){
                    showdown_histogram_counter[round][showdown_histogram_2_idx[round].at(showdown_histogram)] += count.size();
                }
            }
            assert(showdown_histogram_counter[round].size() == showdown_histograms[round].size());
            assert(showdown_histogram_2_idx[round].size() == showdown_histograms[round].size());
            BOOST_LOG_TRIVIAL(debug) << "showdown_histogram size: "<<showdown_histograms[round].size();

            // 将该轮次每个isomorphism 映射到 showdown/potential histogram的idx
            bu_potential_coordinate[round].clear();
            bu_potential_coordinate[round].resize(Hand<Poker_t>::get_isomorphism_size(round, round));
            std::fill(bu_potential_coordinate[round].begin(), bu_potential_coordinate[round].end(), -1);
            bu_showdown_coordinate[round].clear();
            bu_showdown_coordinate[round].resize(Hand<Poker_t>::get_isomorphism_size(round, round));
            std::fill(bu_showdown_coordinate[round].begin(), bu_showdown_coordinate[round].end(), -1);
            for (int th = 0; th < overall_define::num_threads; ++th){
                sp_pool->enqueue([ &bu_potential_coordinate = bu_potential_coordinate[round]
                                 , &potential_histogram_maps
                                 , &potential_histogram_2_idx = potential_histogram_2_idx[round]
                                 , &bu_showdown_coordinate = bu_showdown_coordinate[round]
                                 , &showdown_histogram_maps
                                 , &showdown_histogram_2_idx = showdown_histogram_2_idx[round]
                                 , th]() {
                    for (const auto& [potential_histogram, counters] : potential_histogram_maps[th]){
                        auto potential_histogram_idx = potential_histogram_2_idx.at(potential_histogram);
                        for (auto isomorphism : counters){
                            bu_potential_coordinate[isomorphism] = potential_histogram_idx;
                        }
                    }

                    for (const auto& [showdown_histogram, counters] : showdown_histogram_maps[th]){
                        auto showdown_histogram_idx = showdown_histogram_2_idx.at(showdown_histogram);
                        for (auto isomorphism : counters){
                            bu_showdown_coordinate[isomorphism] = showdown_histogram_idx;
                        }
                    }
                });
            }

            // 线程等待每个区间
            wait_necessary();
        }

        auto end_t = std::chrono::high_resolution_clock::now();
        int duration_t = std::chrono::duration_cast<std::chrono::milliseconds>(end_t - start_t).count();
        printf("recall_early_game round %d cost %d ms\n", round, duration_t);

        recall_early_game<round-1>();
    }
    // to delete
    // if (round == 1) {
    //     printf("begin\n");
    //     for(const auto& potential_histogram : potential_histograms[round]){
    //         for(const auto& histogram_frac: potential_histogram){
    //             printf("%ld,",histogram_frac);
    //         }
    //         printf("\n");
    //     }
    // }
    // to delete end

    // to delete
    // if (round == 1) {
    //     printf("begin\n");
    //     for(const auto& showdown_histogram : showdown_histograms[round]){
    //         for(const auto& histogram_frac: showdown_histogram){
    //             printf("%ld,",histogram_frac);
    //         }
    //         printf("\n");
    //     }
    // }
    // to delete end

    // to delete
    // if (round == 2) {
    //     printf("begin\n");
    //     for(const auto& i : bu_showdown_coordinate[round]){
    //         printf("%ld\n", i);
    //     }
    // }
    // to delete end
}

template<typename Poker>
template<int round>
void AbstractionLimit<Poker>::explore_late_game(){
    if constexpr(round > Poker_t::num_rounds-1)
        return;
    else {
        BOOST_LOG_TRIVIAL(debug) << "explore_late_game round: "<< round;

        auto start_t = std::chrono::high_resolution_clock::now();

        if constexpr (round == 0) {
            // 对于第一轮次，因为不存在上一轮次了直接从imperfect recall数据转换
            showdown_perfectrecall_features[round].clear();
            showdown_perfectrecall_features[round].reserve(showdown_histograms[round].size());
            showdown_perfectrecall_feature_2_idx[round].clear();
            for(uint64_t i = 0, sz = showdown_histograms[round].size(); i < sz; ++i){
                std::vector<uint64_t> showdown_perfectrecall_feature{i};
                showdown_perfectrecall_features[round].push_back(showdown_perfectrecall_feature);
                showdown_perfectrecall_feature_2_idx[round].insert({showdown_perfectrecall_feature, i});
            }
            showdown_perfectrecall_feature_counter[round] = showdown_histogram_counter[round];
            bub_showdown_coordinate[round] = bu_showdown_coordinate[round];

            potential_perfectrecall_features[round].clear();
            potential_perfectrecall_features[round].reserve(potential_histograms[round].size());
            potential_perfectrecall_feature_2_idx[round].clear();
            for(uint64_t i = 0, sz = potential_histograms[round].size(); i < sz; ++i){
                std::vector<uint64_t> potential_perfectrecall_feature{i};
                potential_perfectrecall_features[round].push_back(potential_perfectrecall_feature);
                potential_perfectrecall_feature_2_idx[round].insert({potential_perfectrecall_feature, i});
            }
            potential_perfectrecall_feature_counter[round] = potential_histogram_counter[round];
            bub_potential_coordinate[round] = bu_potential_coordinate[round];
        }
        else {

            std::vector< type::hash_table< std::vector<uint64_t>, std::vector<uint64_t>, ContainerHash<std::vector<uint64_t>>>> showdown_perfectrecall_feature_maps(overall_define::num_threads);
            std::vector< type::hash_table< std::vector<uint64_t>, std::vector<uint64_t>, ContainerHash<std::vector<uint64_t>>>> potential_perfectrecall_feature_maps(overall_define::num_threads);
            showdown_perfectrecall_features[round].clear();
            potential_perfectrecall_features[round].clear();

            auto compute_feature_func = [&showdown_perfectrecall_feature_maps, &potential_perfectrecall_feature_maps, this](int thread_idx, uint64_t step_begin, uint64_t step_end) {

                type::card_t hand_card[Poker_t::hand_len[round]];

                for(uint64_t i = step_begin; i < step_end; ++i){ 
                    Hand<Poker_t>::hand_unisomorphism(i, round, 0, hand_card); // 拿到isomorphism i对应的牌
                    
                    if constexpr (Poker_t::deal_hole_round[round] > 0) { // Poker_t::hole_len[round-1] + Poker_t::board_len[round-1] 变成上一轮的牌
                        std::copy(hand_card + Poker_t::hole_len[round], hand_card + Poker_t::hole_len[round] + Poker_t::board_len[round-1], hand_card + Poker_t::hole_len[round-1]);
                    }

                    uint64_t last_perfect_isomorphism = Hand<Poker_t>::compute_hand_isomorphism(round-1, 0, hand_card);
                    uint64_t imperfect_isomorphism = Hand<Poker_t>::compute_hand_isomorphism(round, round, hand_card);

                    // 根据上一轮次同构拿到两种坐标
                    std::vector<uint64_t> showdown_perfectrecall_feature = showdown_perfectrecall_features[round-1][ bub_showdown_coordinate[round-1][last_perfect_isomorphism] ];
                    std::vector<uint64_t> potential_perfectrecall_feature = potential_perfectrecall_features[round-1][ bub_potential_coordinate[round-1][last_perfect_isomorphism] ];

                    showdown_perfectrecall_feature.push_back(bu_showdown_coordinate[round][imperfect_isomorphism]);
                    if constexpr (round == Poker_t::num_rounds-1) {
                        potential_perfectrecall_feature.push_back(bu_showdown_coordinate[round][imperfect_isomorphism]); // 最后一层是没有potential的，直接读取本层的showdown
                    }
                    else {
                        potential_perfectrecall_feature.push_back(bu_potential_coordinate[round][imperfect_isomorphism]);
                    }

                    if (showdown_perfectrecall_feature_maps[thread_idx].contains(showdown_perfectrecall_feature)){
                        showdown_perfectrecall_feature_maps[thread_idx].at(showdown_perfectrecall_feature).push_back(i);
                    }
                    else{
                        showdown_perfectrecall_feature_maps[thread_idx].insert({showdown_perfectrecall_feature, {i}});
                    }

                    if (potential_perfectrecall_feature_maps[thread_idx].contains(potential_perfectrecall_feature)){
                        potential_perfectrecall_feature_maps[thread_idx].at(potential_perfectrecall_feature).push_back(i);
                    }
                    else{
                        potential_perfectrecall_feature_maps[thread_idx].insert({potential_perfectrecall_feature, {i}});
                    }
                }
                BOOST_LOG_TRIVIAL(debug) << "potential_perfectrecall_feature_maps "<< thread_idx<< " size: "<< potential_perfectrecall_feature_maps[thread_idx].size();
                BOOST_LOG_TRIVIAL(debug) << "showdown_perfectrecall_feature_maps "<< thread_idx<< " size: "<< showdown_perfectrecall_feature_maps[thread_idx].size();
            };

            const uint64_t isomorphism_size = Hand<Poker_t>::get_isomorphism_size(round, 0);
            const uint64_t step = (isomorphism_size + overall_define::num_threads - 1) / overall_define::num_threads;
            for (uint64_t real_step_begin = 0; real_step_begin < isomorphism_size; real_step_begin += step) {

                int th = real_step_begin/step;
                uint64_t real_step_end = (real_step_begin + step > isomorphism_size) ? isomorphism_size : real_step_begin + step;
                auto bind_fun = std::bind(compute_feature_func, th, real_step_begin, real_step_end);

                sp_pool->enqueue(bind_fun);
            }

            // 线程等待每个区间
            wait_necessary();

            // 将各个线程汇总,由于是按照线程编号积累，如果hash_table的iterator是按照插入顺序遍历那么与开一个线程的顺序一样（这个假设有可能不成立）
            potential_perfectrecall_feature_counter[round].clear();
            potential_perfectrecall_feature_2_idx[round].clear();
            potential_perfectrecall_features[round].clear();
            for(uint64_t th = 0, idx = 0; th < overall_define::num_threads; ++th){

                for(const auto& [potential_perfectrecall_feature, count] : potential_perfectrecall_feature_maps[th]) {
                    if(potential_perfectrecall_feature_2_idx[round].contains(potential_perfectrecall_feature)){
                        potential_perfectrecall_feature_counter[round][potential_perfectrecall_feature_2_idx[round].at(potential_perfectrecall_feature)] += count.size();
                    }
                    else {
                        potential_perfectrecall_features[round].push_back(potential_perfectrecall_feature); //注册新的特征
                        const auto& [_, res] = potential_perfectrecall_feature_2_idx[round].insert({potential_perfectrecall_feature, idx}); // 对特征生成唯一坐标
                        assert(res == true);
                        potential_perfectrecall_feature_counter[round].push_back(count.size()); // 统计特征个数
                        ++idx;
                    }
                }
            }
            assert(potential_perfectrecall_feature_counter[round].size() == potential_perfectrecall_features[round].size());
            assert(potential_perfectrecall_feature_2_idx[round].size() == potential_perfectrecall_features[round].size());
            BOOST_LOG_TRIVIAL(debug) << "potential_perfectrecall_feature size: "<<potential_perfectrecall_features[round].size();

            showdown_perfectrecall_feature_counter[round].clear();
            showdown_perfectrecall_feature_2_idx[round].clear();
            showdown_perfectrecall_features[round].clear();
            for(uint64_t th = 0, idx = 0; th < overall_define::num_threads; ++th){

                for(const auto& [showdown_perfectrecall_feature, count] : showdown_perfectrecall_feature_maps[th]) {
                    if(showdown_perfectrecall_feature_2_idx[round].contains(showdown_perfectrecall_feature)){
                        showdown_perfectrecall_feature_counter[round][showdown_perfectrecall_feature_2_idx[round].at(showdown_perfectrecall_feature)] += count.size();
                    }
                    else {
                        showdown_perfectrecall_features[round].push_back(showdown_perfectrecall_feature); //注册新的特征
                        const auto& [_, res] = showdown_perfectrecall_feature_2_idx[round].insert({showdown_perfectrecall_feature, idx}); // 对特征生成唯一坐标
                        assert(res == true);
                        showdown_perfectrecall_feature_counter[round].push_back(count.size()); // 统计特征个数
                        ++idx;
                    }
                }
            }
            assert(showdown_perfectrecall_feature_counter[round].size() == showdown_perfectrecall_features[round].size());
            assert(showdown_perfectrecall_feature_2_idx[round].size() == showdown_perfectrecall_features[round].size());
            BOOST_LOG_TRIVIAL(debug) << "showdown_perfectrecall_feature size: "<<showdown_perfectrecall_features[round].size();

            // 将该轮次每个isomorphism 映射到 showdown/potential feature的idx
            bub_potential_coordinate[round].clear();
            bub_potential_coordinate[round].resize(Hand<Poker_t>::get_isomorphism_size(round, 0));
            std::fill(bub_potential_coordinate[round].begin(), bub_potential_coordinate[round].end(), -1);
            bub_showdown_coordinate[round].clear();
            bub_showdown_coordinate[round].resize(Hand<Poker_t>::get_isomorphism_size(round, 0));
            std::fill(bub_showdown_coordinate[round].begin(), bub_showdown_coordinate[round].end(), -1);
            for (int th = 0; th < overall_define::num_threads; ++th){
                sp_pool->enqueue([ &bub_potential_coordinate = bub_potential_coordinate[round]
                                 , &potential_perfectrecall_feature_maps
                                 , &potential_perfectrecall_feature_2_idx = potential_perfectrecall_feature_2_idx[round]
                                 , &bub_showdown_coordinate = bub_showdown_coordinate[round]
                                 , &showdown_perfectrecall_feature_maps
                                 , &showdown_perfectrecall_feature_2_idx = showdown_perfectrecall_feature_2_idx[round]
                                 , th]() {
                    for (const auto& [potential_perfectrecall_feature, counters] : potential_perfectrecall_feature_maps[th]){
                        auto potential_perfectrecall_feature_idx = potential_perfectrecall_feature_2_idx.at(potential_perfectrecall_feature);
                        for (auto isomorphism : counters){
                            bub_potential_coordinate[isomorphism] = potential_perfectrecall_feature_idx;
                        }
                    }

                    for (const auto& [showdown_perfectrecall_feature, counters] : showdown_perfectrecall_feature_maps[th]){
                        auto showdown_perfectrecall_feature_idx = showdown_perfectrecall_feature_2_idx.at(showdown_perfectrecall_feature);
                        for (auto isomorphism : counters){
                            bub_showdown_coordinate[isomorphism] = showdown_perfectrecall_feature_idx;
                        }
                    }
                });
            }

            wait_necessary();
        }

        auto end_t = std::chrono::high_resolution_clock::now();
        int duration_t = std::chrono::duration_cast<std::chrono::milliseconds>(end_t - start_t).count();
        printf("explore_late_game round %d cost %d ms\n", round, duration_t);

        explore_late_game<round+1>();
    }
}

#include <iostream>

template<typename Poker>
void AbstractionLimit<Poker>::write(const char* dir) const {
    std::fstream fout(std::string(dir) + "/outline.json", std::ios::out);
    fout << result();
    fout.close();

    // 打表数据的存储：每轮次isomorphism_size: int64_t, bucket_size: int64_t, bucket_coordinate[isomorphism_size]: uint64_t
    // 考虑下轮次分布的情况
    fout.open(std::string(dir) + "/bu_potential.bin", std::ios::out | std::ios::binary);
    for(int r = 0; r < Poker_t::num_rounds; ++r){
        int isomorphism_size = Hand<Poker_t>::get_isomorphism_size(r, r);
        fout.write((const char*) &isomorphism_size, sizeof(int64_t));
        int64_t size = potential_histogram_counter[r].size();
        fout.write((const char*) &size, sizeof(int64_t));
        fout.write((const char*) bu_potential_coordinate[r].data(), sizeof(uint64_t) * isomorphism_size);
    }
    fout.close();

    fout.open(std::string(dir) + "/bub_potential.bin", std::ios::out | std::ios::binary);
    for(int r = 0; r < Poker_t::num_rounds; ++r){
        int isomorphism_size = Hand<Poker_t>::get_isomorphism_size(r, 0);
        fout.write((const char*) &isomorphism_size, sizeof(int64_t));
        int64_t size = potential_perfectrecall_feature_counter[r].size();
        fout.write((const char*) &size, sizeof(int64_t));
        fout.write((const char*) bub_potential_coordinate[r].data(), sizeof(uint64_t) * isomorphism_size);
    }
    fout.close();

    // 只考虑每轮负平胜的情况
    fout.open(std::string(dir) + "/bu_showdown.bin", std::ios::out | std::ios::binary);
    for(int r = 0; r < Poker_t::num_rounds; ++r){
        int isomorphism_size = Hand<Poker_t>::get_isomorphism_size(r, r);
        fout.write((const char*) &isomorphism_size, sizeof(int64_t));
        int64_t size = showdown_histogram_counter[r].size();
        fout.write((const char*) &size, sizeof(int64_t));
        fout.write((const char*) bu_showdown_coordinate[r].data(), sizeof(uint64_t) * isomorphism_size);
    }
    fout.close();

    fout.open(std::string(dir) + "/bub_showdown.bin", std::ios::out | std::ios::binary);
    for(int r = 0; r < Poker_t::num_rounds; ++r){
        int isomorphism_size = Hand<Poker_t>::get_isomorphism_size(r, 0);
        fout.write((const char*) &isomorphism_size, sizeof(int64_t));
        int64_t size = showdown_perfectrecall_feature_counter[r].size();
        fout.write((const char*) &size, sizeof(int64_t));
        fout.write((const char*) bub_showdown_coordinate[r].data(), sizeof(uint64_t) * isomorphism_size);
    }
    fout.close();

    // fout.open(std::string(dir) + "/bu_showdown.bin", std::ios::in | std::ios::binary);
    // for(int64_t r = 0, iso_size; fout.read((char*) &iso_size, sizeof(int64_t)); ++r){
    //     std::cout<<"round :" <<r<<std::endl;
    //     std::cout<<"isomorphism size: "<<(iso_size == Poker_t::num_hand_isomorphism_round[r])<<std::endl;

    //     int64_t size;
    //     fout.read((char*) &size, sizeof(int64_t)); //  = showdown_histogram_counter[r].size()
    //     std::cout<<"buttom up size: "<<(size == showdown_histogram_counter[r].size())<<std::endl;

    //     std::vector<uint64_t> vec;
    //     vec.resize(iso_size);
    //     fout.read((char*) vec.data(), sizeof(uint64_t) * Poker_t::num_hand_isomorphism_round[r]);
    //     std::cout<<"vector size: "<<std::equal(vec.begin(), vec.end(), bu_showdown_coordinate[r].begin())<<std::endl;
    // }
    // fout.close();
}