#pragma once

#include "template/domain_independent/strategy.hpp"
#include "template/domain_dependent/evaluator_base.h"
#include "util/combinatorics.h"

template<typename Poker_t>
void accumulate_evs( const Strategy<Poker_t> strategies[Poker_t::num_players]
                   , const Sequence<Poker_t>& seq
                   , double reach
                   , double ev[Poker_t::num_players]
                   , 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 deck[Poker_t::deck_len]
                   , uint64_t round_buckets[Poker_t::num_players]
                   ){
    if (seq.is_terminal()){

        double award[Poker_t::num_players];

        if (seq.is_fold()) {

            seq.deal_fold(award);
        }
        else {

            type::rank_t ranks[Poker_t::num_players];
            Evaluator<Poker_t>::evaluate_ranks(holes, board, ranks);
            seq.deal_showdown(ranks, award);
        }

        for(int i = 0; i<Poker_t::num_players; ++i){

            ev[i] += award[i] * reach;
        }
    }
    else {
        
        int u           = seq.get_id();
        int player      = Game<Poker_t>::whose_turn[u];
        int round       = Game<Poker_t>::round[u];
        int action_size = Game<Poker_t>::num_actions[u];

        const double *probability = strategies[player].get_strategy(seq, round_buckets[player]);

        for(int i = 0; i<action_size; ++i){
            Sequence<Poker_t> next_seq = seq.do_action(i);
            if(int next_u = next_seq.get_id(); next_u<Game<Poker_t>::num_internal && Game<Poker_t>::round[next_u]!= round){
                dealing_chance_evs<Poker_t>(strategies, next_seq, reach * probability[i], ev, holes, board, deck);
            }
            else{
                accumulate_evs<Poker_t>( strategies, next_seq, reach * probability[i], ev, holes, board, deck, round_buckets);
            }
        }
    }

}

template<typename Poker_t>
void dealed_chance_evs( const Strategy<Poker_t> strategies[Poker_t::num_players]
                      , const Sequence<Poker_t>& seq
                      , double reach
                      , double ev[Poker_t::num_players]
                      , 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 deck[Poker_t::deck_len]
                      , type::card_t* deal_begin
                      , type::card_t* deal_end
                      ){
    const int round = Game<Poker_t>::round[seq.get_id()];
    const type::card_t* copied_idx = deal_begin;

    if(Poker_t::deal_hole_round[round]>0){

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

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

    assert(copied_idx == deal_end);

    Hand<Poker_t> temp_hand(holes[0], board, round);
    uint64_t round_buckets[Poker_t::num_players];
    round_buckets[0] = temp_hand.get_hand_isomorphism(0);
    for(int i = 1; i<Poker_t::num_players; ++i){
        temp_hand.change_hole(holes[i]);
        round_buckets[i] = temp_hand.get_hand_isomorphism(0);
    }

    accumulate_evs(strategies, seq, reach, ev, holes, board, deck, round_buckets);

}

template<typename Poker_t>
void dealing_chance_evs( const Strategy<Poker_t> strategies[Poker_t::num_players]
                       , const Sequence<Poker_t>& seq
                       , double reach
                       , double ev[Poker_t::num_players]
                       , 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 deck[Poker_t::deck_len]
                       ){
    int round = Game<Poker_t>::round[seq.get_id()];
    double pi_c = 1./Poker_t::deal_combine_num_round[round];
    auto dealed_chance_evs_ = std::bind(dealed_chance_evs<Poker_t>, strategies, std::ref(seq), reach*pi_c, ev, holes, board, deck, std::placeholders::_1, std::placeholders::_2);
    combinatorics::for_multi_combinations(dealed_chance_evs_, deck + Poker_t::dealed_card_num_round[round], deck + Poker_t::deck_len, std::span<const type::card_t>{&Poker_t::deal_combine[Poker_t::deal_combine_begin_round[round]], &Poker_t::deal_combine[Poker_t::deal_combine_end_round[round]]});
}

template<typename Poker_t>
void accumulate_evs(const Strategy<Poker_t> strategies[Poker_t::num_players], double ev[Poker_t::num_players]){

    type::card_t deck[Poker_t::deck_len];
    std::copy(Poker_t::deck.begin(), Poker_t::deck.end(), deck);

    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]];

    dealing_chance_evs<Poker_t>(strategies, Sequence<Poker_t>(0), 1.0, ev, holes, board, deck);

}

