#pragma once

#include "template/domain_independent/strategy.hpp"

template<typename Poker>
class PlayerTerminalReach{ //不包括chance的概率
public:
    using Poker_t = Poker;

    PlayerTerminalReach(int player);
    ~PlayerTerminalReach();

    int get_player() const;
    double get_reach(int u, uint64_t isomorphism) const;
    void compute_reach(const Strategy<Poker_t>& strategy);
    friend void recursive_compute_reach<Poker_t>( PlayerTerminalReach<Poker_t>& player_terminal_reach
                                                , const Strategy<Poker_t>& strategy
                                                , const Sequence<Poker_t>& seq
                                                , double reach
                                                , uint64_t round_buckets[Poker_t::num_rounds]);
    void reset_(){
        for(int i = 0; i < Game<Poker_t>::num_terminal; ++i){
            int u = i + Game<Poker_t>::num_internal;

            //拿到terminal节点由哪轮internal节点导出, 
            int round = Game<Poker_t>::round[u];

            uint64_t n = Hand<Poker_t>::get_isomorphism_size(round, 0);
            std::fill(probability[i], probability[i] + n, -1.0);
        }
    }

protected:
    double** probability;
    int player;
};

template<typename Poker_t>
PlayerTerminalReach<Poker_t>::PlayerTerminalReach(int player):player(player){
    probability = new double*[Game<Poker_t>::num_terminal];
    std::memset(probability, 0, sizeof(double*) * Game<Poker_t>::num_terminal);

    for(int i = 0; i < Game<Poker_t>::num_terminal; ++i){
        int u = i + Game<Poker_t>::num_internal;

        //拿到terminal节点由哪轮internal节点导出, 
        int round = Game<Poker_t>::round[u];

        uint64_t n = Hand<Poker_t>::get_isomorphism_size(round, 0);
        probability[i] = new double[n];
    }
}

template<typename Poker_t>
PlayerTerminalReach<Poker_t>::~PlayerTerminalReach(){
    if (probability) {
        for(int i = 0; i < Game<Poker_t>::num_terminal; ++i){
            
            delete[] probability[i];
        }
        std::memset(probability, 0, sizeof(double*) * Game<Poker_t>::num_terminal);
        
        delete[] probability;
        probability = nullptr;
    }
}

template<typename Poker_t>
void recursive_compute_reach( PlayerTerminalReach<Poker_t>& player_terminal_reach
                            , const Strategy<Poker_t>& strategy
                            , const Sequence<Poker_t>& seq
                            , double reach
                            , uint64_t round_buckets[Poker_t::num_rounds]
                            ){
    int u = seq.get_id();
    int round = Game<Poker_t>::round[u];
    if(seq.is_terminal()) {
        int terminal_id = u-Game<Poker_t>::num_internal;
        if(player_terminal_reach.probability[terminal_id][round_buckets[round]] > 0) {
            assert(player_terminal_reach.probability[terminal_id][round_buckets[round]] == reach);
        }
        else {
            player_terminal_reach.probability[terminal_id][round_buckets[round]] = reach;
        }
    }
    else {
        const double *this_player_tuple = nullptr;
        if(Game<Poker_t>::whose_turn[u] == player_terminal_reach.player)
            this_player_tuple = strategy.get_strategy(seq, round_buckets[round]);
        for(int i = 0; i < Game<Poker_t>::num_actions[u]; ++i) {
            double action_probability = 1.0;
            if (this_player_tuple) {
                assert(Game<Poker_t>::whose_turn[u] == player_terminal_reach.player);
                action_probability *= this_player_tuple[i];
            }
            recursive_compute_reach(player_terminal_reach, strategy, seq.do_action(i), reach * action_probability, round_buckets);
        }
    }
}

template<typename Poker>
void PlayerTerminalReach<Poker>::compute_reach(const Strategy<Poker_t>& strategy){
    reset_();
    type::card_t hand_card[Poker_t::hand_len[Poker_t::num_rounds-1]];
    uint64_t buckets[Poker_t::num_rounds];
    for (uint64_t i = 0, isomorphism_size = Hand<Poker_t>::get_isomorphism_size(Poker_t::num_rounds-1, 0); i<isomorphism_size; ++i){
        Hand<Poker_t>::hand_unisomorphism(i, Poker_t::num_rounds-1, 0, hand_card);
        for(int r = 0, round_size = Poker_t::num_rounds; r < round_size; ++r){
            Hand<Poker_t> hand(hand_card, hand_card + Poker_t::hole_len[round_size-1], r);
            buckets[r] = hand.get_hand_isomorphism(0);
        }
        recursive_compute_reach<Poker_t>(*this, strategy, Sequence<Poker_t>(0), 1.0, buckets);
    }
}

template<typename Poker>
double PlayerTerminalReach<Poker>::get_reach(int u, uint64_t isomorphism) const {
    return probability[u-Game<Poker_t>::num_internal][isomorphism];
}