#pragma once
#include "overall_define.h"
#include "util/combinatorics.h"
#include <tlx/thread_pool.hpp>
#include <memory>
#include <functional>
#include <forward_list>

namespace iteration{
    template<typename Poker_t>
    std::array<std::forward_list<int>, Poker_t::num_rounds> get_seqs_result_from_last_round(){
        std::array<std::forward_list<int>, Poker_t::num_rounds> seqs_result_from_last_round;
        for(int u = 0; u < Game<Poker_t>::num_total; ++u){
            int parent = Game<Poker_t>::parent[u];
            int round = Game<Poker_t>::round[u];
            if( parent == -1 || Game<Poker_t>::round[parent] < round){
                seqs_result_from_last_round[round].push_front(u);
            }
        }

        return seqs_result_from_last_round;
    }

    template<typename Poker_t>
    void dealed_hands_from_round_to_round( const std::function<void(type::card_t[Poker_t::num_players][Poker_t::hole_len[Poker_t::num_rounds-1]], type::card_t[Poker_t::board_len[Poker_t::num_rounds-1]])>& func
                                        , int round_begin
                                        , int round_end
                                        , type::card_t holes[Poker_t::num_players][Poker_t::hole_len[Poker_t::num_rounds-1]]
                                        , type::card_t board[Poker_t::board_len[Poker_t::num_rounds-1]]
                                        , type::card_t* deal_begin
                                        , type::card_t* deal_end){
        const type::card_t* copied_idx = deal_begin;
        for(int r = round_begin; r<round_end; ++r){
            if(Poker_t::deal_hole_round[r]>0){

                for(int i = 0; i<Poker_t::num_players; ++i){
                    std::copy(copied_idx, copied_idx + Poker_t::deal_hole_round[r], holes[i]+(Poker_t::hole_len[r]-Poker_t::deal_hole_round[r]));
                    copied_idx += Poker_t::deal_hole_round[r];
                }
            }

            if(Poker_t::deal_board_round[r]>0){
                std::copy(copied_idx, copied_idx + Poker_t::deal_board_round[r], board+ (Poker_t::board_len[r]-Poker_t::deal_board_round[r]));
                copied_idx += Poker_t::deal_board_round[r];
            }
        }
        assert(copied_idx == deal_end);

        func(holes, board);
    }

    template<typename Poker_t>
    void dealing_upto_current_round_hands( const std::function<void(type::card_t[Poker_t::num_players][Poker_t::hole_len[Poker_t::num_rounds-1]], type::card_t[Poker_t::board_len[Poker_t::num_rounds-1]])>& func
                                         , int round
                                         ){
        static std::array deck_a = Poker_t::deck;
        static type::card_t* deck = deck_a.data();

        static type::card_t holes[Poker_t::num_players][Poker_t::hole_len[Poker_t::num_rounds-1]];
        static type::card_t board[Poker_t::board_len[Poker_t::num_rounds-1]];

        auto dealed_upto_current_round_hands = std::bind(dealed_hands_from_round_to_round<Poker_t>, std::cref(func), 0, round+1, holes, board, std::placeholders::_1, std::placeholders::_2);
        combinatorics::for_multi_combinations(dealed_upto_current_round_hands, deck + Poker_t::dealed_card_num_round[0], deck + Poker_t::deck_len, std::span<const type::card_t>{&Poker_t::deal_combine[0], &Poker_t::deal_combine[Poker_t::deal_combine_end_round[round]]});
    }

    // 第一街的组合用来创建线程，后几层的组合都是在同一个线程中进行的
    template<typename Poker_t>
    void multithread_dealing_upto_current_round_hands( std::shared_ptr<tlx::ThreadPool> sp_pool
                                                     , const std::function<void( type::card_t[Poker_t::num_players][Poker_t::hole_len[Poker_t::num_rounds-1]]
                                                                               , type::card_t[Poker_t::board_len[Poker_t::num_rounds-1]]
                                                                               )
                                                                          >& func
                                                     , int round
                                                     ){
        static std::array deck_a = Poker_t::deck;
        static type::card_t* deck = deck_a.data();
    
        combinatorics::for_multi_combinations( [&overall_deck = deck, &func, &sp_pool, round](const type::card_t* deal_begin, const type::card_t* deal_end){
                                                    type::card_t* deck = new type::card_t[Poker_t::deck_len - Poker_t::dealed_card_num_round[1]]; //各个线程使用独立的deck
                                                    std::copy(overall_deck + Poker_t::dealed_card_num_round[1], overall_deck + Poker_t::deck_len, deck);

                                                    type::card_t (*holes)[Poker_t::hole_len[Poker_t::num_rounds-1]] = new type::card_t[Poker_t::num_players][Poker_t::hole_len[Poker_t::num_rounds-1]];
                                                    type::card_t *board = new type::card_t[Poker_t::board_len[Poker_t::num_rounds-1]];

                                                    const type::card_t* copied_idx = deal_begin;
                                                    {
                                                        int r = 0;// 只分round = 0的牌
                                                        if(Poker_t::deal_hole_round[r]>0){

                                                            for(int i = 0; i<Poker_t::num_players; ++i){
                                                                std::copy(copied_idx, copied_idx + Poker_t::deal_hole_round[r], holes[i]+(Poker_t::hole_len[r]-Poker_t::deal_hole_round[r]));
                                                                copied_idx += Poker_t::deal_hole_round[r];
                                                            }
                                                        }

                                                        if(Poker_t::deal_board_round[r]>0){
                                                            std::copy(copied_idx, copied_idx + Poker_t::deal_board_round[r], board+ (Poker_t::board_len[r]-Poker_t::deal_board_round[r]));
                                                            copied_idx += Poker_t::deal_board_round[r];
                                                        }
                                                    }
                                                    assert(copied_idx == deal_end);

                                                    sp_pool->enqueue([deck, &func, holes, board, round](){

                                                        auto dealed_upto_current_round_hands = std::bind(dealed_hands_from_round_to_round<Poker_t>, std::cref(func), 1, round+1, holes, board, std::placeholders::_1, std::placeholders::_2);
                                                        combinatorics::for_multi_combinations(dealed_upto_current_round_hands, deck , deck + Poker_t::deck_len - Poker_t::dealed_card_num_round[1], std::span<const type::card_t>{&Poker_t::deal_combine[Poker_t::deal_combine_begin_round[1]], &Poker_t::deal_combine[Poker_t::deal_combine_end_round[round]]});
                                                        delete[] deck;
                                                        delete[] holes;
                                                        delete[] board;
                                                    });
                                               }
                                             , deck + Poker_t::dealed_card_num_round[0]
                                             , deck + Poker_t::deck_len
                                             , std::span<const type::card_t>{&Poker_t::deal_combine[0], &Poker_t::deal_combine[Poker_t::deal_combine_end_round[0]]}); //Poker_t::deal_combine_end_round[round]
        
        sp_pool-> loop_until_empty();
    }

    template<typename Poker_t>
    void dealed_hand_from_round_to_round( const std::function<void( type::card_t[Poker_t::hole_len[Poker_t::num_rounds-1]]
                                                                  , type::card_t[Poker_t::board_len[Poker_t::num_rounds-1]]
                                                                  )
                                                             >& func
                                        , int round_begin
                                        , int round_end
                                        , type::card_t hole[Poker_t::hole_len[Poker_t::num_rounds-1]]
                                        , type::card_t board[Poker_t::board_len[Poker_t::num_rounds-1]]
                                        , type::card_t* deal_begin
                                        , type::card_t* deal_end){
        const type::card_t* copied_idx = deal_begin;
        for(int r = round_begin; r<round_end; ++r){
            if(Poker_t::deal_hole_round[r]>0){
                std::copy(copied_idx, copied_idx + Poker_t::deal_hole_round[r], hole + (Poker_t::hole_len[r]-Poker_t::deal_hole_round[r]));
                copied_idx += Poker_t::deal_hole_round[r];
            }

            if(Poker_t::deal_board_round[r]>0){
                std::copy(copied_idx, copied_idx + Poker_t::deal_board_round[r], board + (Poker_t::board_len[r]-Poker_t::deal_board_round[r]));
                copied_idx += Poker_t::deal_board_round[r];
            }
        }
        assert(copied_idx == deal_end);

        func(hole, board);
    }

    template<typename Poker_t>
    void dealing_upto_current_round_hand( const std::function<void( type::card_t[Poker_t::hole_len[Poker_t::num_rounds-1]]
                                                                  , type::card_t[Poker_t::board_len[Poker_t::num_rounds-1]]
                                                                  )
                                                             >& func
                                        , int round
                                        ){
        static std::array deck_a = Poker_t::deck;
        static type::card_t* deck = deck_a.data();

        static type::card_t hole[Poker_t::hole_len[Poker_t::num_rounds-1]];
        static type::card_t board[Poker_t::board_len[Poker_t::num_rounds-1]];

        auto dealed_upto_current_round_hand = std::bind(dealed_hand_from_round_to_round<Poker_t>, std::cref(func), 0, round+1, hole, board, std::placeholders::_1, std::placeholders::_2);
        combinatorics::for_multi_combinations(dealed_upto_current_round_hand, deck + Poker_t::single_dealed_card_num_round[0], deck + Poker_t::deck_len, std::span<const type::card_t>{&Poker_t::single_deal_combine[0], &Poker_t::single_deal_combine[Poker_t::single_deal_combine_end_round[round]]});
    }

    template<typename Poker_t>
    void multithread_dealing_upto_current_round_hand( std::shared_ptr<tlx::ThreadPool> sp_pool
                                                    , const std::function<void( type::card_t[Poker_t::hole_len[Poker_t::num_rounds-1]]
                                                                              , type::card_t[Poker_t::board_len[Poker_t::num_rounds-1]]
                                                                              )
                                                                         >& func
                                                    , int round
                                                    ){
        static std::array deck_a = Poker_t::deck;
        static type::card_t* deck = deck_a.data();
    
        combinatorics::for_multi_combinations( [&overall_deck = deck, &func, &sp_pool, round](const type::card_t* deal_begin, const type::card_t* deal_end){
                                                    type::card_t* deck = new type::card_t[Poker_t::deck_len - Poker_t::single_dealed_card_num_round[1]]; //各个线程使用独立的deck
                                                    std::copy(overall_deck + Poker_t::single_dealed_card_num_round[1], overall_deck + Poker_t::deck_len, deck);

                                                    type::card_t *hole= new type::card_t[Poker_t::hole_len[Poker_t::num_rounds-1]];
                                                    type::card_t *board = new type::card_t[Poker_t::board_len[Poker_t::num_rounds-1]];

                                                    const type::card_t* copied_idx = deal_begin;
                                                    {
                                                        int r = 0;// 只分round = 0的牌
                                                        if(Poker_t::deal_hole_round[r]>0){
                                                            std::copy(copied_idx, copied_idx + Poker_t::deal_hole_round[r], hole +(Poker_t::hole_len[r]-Poker_t::deal_hole_round[r]));
                                                            copied_idx += Poker_t::deal_hole_round[r];
                                                        }

                                                        if(Poker_t::deal_board_round[r]>0){
                                                            std::copy(copied_idx, copied_idx + Poker_t::deal_board_round[r], board + (Poker_t::board_len[r]-Poker_t::deal_board_round[r]));
                                                            copied_idx += Poker_t::deal_board_round[r];
                                                        }
                                                    }
                                                    assert(copied_idx == deal_end);

                                                    sp_pool->enqueue([deck, &func, hole, board, round](){

                                                        auto dealed_upto_current_round_hand = std::bind(dealed_hand_from_round_to_round<Poker_t>, std::cref(func), 1, round+1, hole, board, std::placeholders::_1, std::placeholders::_2);
                                                        combinatorics::for_multi_combinations(dealed_upto_current_round_hand, deck , deck + Poker_t::deck_len - Poker_t::single_dealed_card_num_round[1], std::span<const type::card_t>{&Poker_t::single_deal_combine[Poker_t::single_deal_combine_begin_round[1]], &Poker_t::single_deal_combine[Poker_t::single_deal_combine_end_round[round]]});
                                                        delete[] deck;
                                                        delete[] hole;
                                                        delete[] board;
                                                    });
                                               }
                                             , deck + Poker_t::single_dealed_card_num_round[0]
                                             , deck + Poker_t::deck_len
                                             , std::span<const type::card_t>{&Poker_t::single_deal_combine[0], &Poker_t::single_deal_combine[Poker_t::single_deal_combine_end_round[0]]}); //Poker_t::deal_combine_end_round[round]
        
        sp_pool-> loop_until_empty();
    }
}
