use rand::prelude::*;
use itertools::Itertools; 
use std::fmt;

// Constants
const NUM_PLAYERS: usize = 9;
const NUM_CARDS: usize = 52;
const NUM_RANKS: usize = 13;
const NUM_SUITS: usize = 4;
const MAX_STACK: i32 = 10000;
const SMALL_BLIND: i32 = 50;
const BIG_BLIND: i32 = 100;

// Actions
#[derive(Clone, Copy, Debug)]
enum Action {
    Fold,
    Call,
    Raise,
}

// Card representation
#[derive(Clone, Copy)]
struct Card {
    rank: usize,
    suit: usize,
}

impl Card {
    fn new(id: usize) -> Card {
        Card {
            suit: id / NUM_RANKS,
            rank: id % NUM_RANKS,
        }
    }
}

impl fmt::Display for Card {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let rank_strings = ["2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K", "A"];
        let suit_strings = ["h", "d", "c", "s"];
        write!(f, "{}{}", rank_strings[self.rank], suit_strings[self.suit])
    }
}

// Deck management
struct Deck {
    cards: Vec<Card>,
    rng: ThreadRng,
}

impl Deck {
    fn new() -> Deck {
        let mut deck = Deck {
            cards: Vec::with_capacity(NUM_CARDS),
            rng: thread_rng(),
        };
        deck.generate_deck();
        deck.shuffle();
        deck
    }

    fn generate_deck(&mut self) {
        self.cards.clear();
        for i in 0..NUM_CARDS {
            self.cards.push(Card::new(i));
        }
    }

    fn shuffle(&mut self) {
        self.cards.shuffle(&mut self.rng);
    }

    fn deal(&mut self) -> Card {
        self.cards.pop().expect("No more cards in the deck")
    }

    fn reset(&mut self) {
        self.generate_deck();
        self.shuffle();
    }
}

// Hand evaluation
struct HandEvaluator;

impl HandEvaluator {
    fn evaluate_hand(hand: &[Card], community_cards: &[Card]) -> u32 {
        let mut all_cards = hand.to_vec();
        all_cards.extend_from_slice(community_cards);

        let card_ints: Vec<u8> = all_cards.iter().map(|card| Self::card_to_int(*card)).collect();
        Self::evaluate_7_card_hand(&card_ints)
    }

    fn card_to_int(card: Card) -> u8 {
        let rank_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41];
        let suit_bits = [0x10, 0x20, 0x40, 0x80];

        let rank_prime = rank_primes[card.rank];
        let suit_bit = suit_bits[card.suit];

        (rank_prime as u8) | suit_bit
    }

    fn evaluate_7_card_hand(cards: &[u8]) -> u32 {
        let mut best_rank = u32::MAX;

        for combo in cards.iter().combinations(5) {
            let hand = [combo[0], combo[1], combo[2], combo[3], combo[4]];
            let rank = Self::evaluate_5_card_hand(&hand);
            if rank < best_rank {
                best_rank = rank;
            }
        }

        best_rank
    }

    fn evaluate_5_card_hand(cards: &[&u8]) -> u32 {
        let mut rank_counts = [0u8; NUM_RANKS];
        let mut suit_counts = [0u8; NUM_SUITS];

        for &&card in cards {
            let rank_prime = (card & 0x0F) as usize;
            let suit_bit = card & 0xF0;

            let rank_index = match rank_prime {
                2 => 0, 3 => 1, 5 => 2, 7 => 3, 11 => 4,
                13 => 5, 17 => 6, 19 => 7, 23 => 8, 29 => 9,
                31 => 10, 37 => 11, 41 => 12, _ => continue,
            };
            rank_counts[rank_index] += 1;

            let suit_index = match suit_bit {
                0x10 => 0, 0x20 => 1, 0x40 => 2, 0x80 => 3, _ => continue,
            };
            suit_counts[suit_index] += 1;
        }

        let is_flush = suit_counts.iter().any(|&count| count >= 5);
        let mut ranks: Vec<u8> = rank_counts.iter().cloned().filter(|&count| count > 0).collect();
        ranks.sort_unstable_by(|a, b| b.cmp(a));

        let hand_rank = if is_flush && Self::is_straight(&rank_counts) {
            1 // Straight Flush
        } else if ranks[0] == 4 {
            2 // Four of a Kind
        } else if ranks[0] == 3 && ranks[1] >= 2 {
            3 // Full House
        } else if is_flush {
            4 // Flush
        } else if Self::is_straight(&rank_counts) {
            5 // Straight
        } else if ranks[0] == 3 {
            6 // Three of a Kind
        } else if ranks[0] == 2 && ranks[1] == 2 {
            7 // Two Pair
        } else if ranks[0] == 2 {
            8 // One Pair
        } else {
            9 // High Card
        };

        hand_rank
    }

    fn is_straight(rank_counts: &[u8; NUM_RANKS]) -> bool {
        let mut consecutive = 0;
        let counts = [&rank_counts[..], &[rank_counts[0]]].concat();
        for &count in counts.iter() {
            if count > 0 {
                consecutive += 1;
                if consecutive >= 5 {
                    return true;
                }
            } else {
                consecutive = 0;
            }
        }
        false
    }
}

// Player structure
struct Player {
    stack: i32,
    bet: i32,
    active: bool,
    all_in: bool,
    hand: Vec<Card>,
}

impl Player {
    fn new(stack: i32) -> Player {
        Player {
            stack,
            bet: 0,
            active: true,
            all_in: false,
            hand: Vec::with_capacity(2),
        }
    }

    fn reset(&mut self) {
        self.bet = 0;
        self.active = true;
        self.all_in = false;
        self.hand.clear();
    }
}

// Observation structure
struct Observation {
    // min_call: i32, // Uncomment if you need to use it in the agent function
    // phase: Phase, // Uncomment if you need to use it in the agent function
}

// Poker environment
struct PokerEnvironment<'a> {
    num_players: usize,
    players: Vec<Player>,
    deck: Deck,
    pot: i32,
    min_call: i32,
    last_raise: i32,
    current_player: usize,
    dealer_position: usize,
    phase: Phase,
    betting_round_over: bool,
    community_cards: Vec<Card>,
    last_aggressor: Option<usize>,
    agent_function: Box<dyn Fn(usize, &Observation) -> Action + 'a>,
}

#[derive(Clone, Copy, Debug, PartialEq)]
enum Phase {
    PreFlop,
    Flop,
    Turn,
    River,
    Showdown,
}

impl<'a> PokerEnvironment<'a> {
    fn new(
        num_players: usize,
        agent_function: Box<dyn Fn(usize, &Observation) -> Action + 'a>,
    ) -> PokerEnvironment<'a> {
        let mut env = PokerEnvironment {
            num_players,
            players: Vec::with_capacity(num_players),
            deck: Deck::new(),
            pot: 0,
            min_call: 0,
            last_raise: 0,
            current_player: 0,
            dealer_position: 0,
            phase: Phase::PreFlop,
            betting_round_over: false,
            community_cards: Vec::new(),
            last_aggressor: None,
            agent_function,
        };
        env.initialize_game();
        env
    }

    fn initialize_game(&mut self) {
        self.players.clear();
        for _ in 0..self.num_players {
            self.players.push(Player::new(MAX_STACK));
        }

        self.deck.reset();

        for player in &mut self.players {
            player.reset();
            player.hand.push(self.deck.deal());
            player.hand.push(self.deck.deal());
        }

        self.pot = 0;
        self.min_call = BIG_BLIND;
        self.last_raise = BIG_BLIND;
        self.current_player = (self.dealer_position + 3) % self.num_players;
        self.phase = Phase::PreFlop;
        self.betting_round_over = false;
        self.community_cards.clear();
        self.last_aggressor = None;

        let small_blind_position = (self.dealer_position + 1) % self.num_players;
        let big_blind_position = (self.dealer_position + 2) % self.num_players;
        self.post_blind(small_blind_position, SMALL_BLIND);
        self.post_blind(big_blind_position, BIG_BLIND);
    }

    fn play_game(&mut self) {
        while !self.is_terminal() {
            let player_id = self.current_player;
            let player = &self.players[player_id];
            if player.active && !player.all_in {
                let observation = self.get_observation();
                let action = (self.agent_function)(player_id, &observation);
                self.step(action);
            } else {
                self.next_player();
            }
        }
        self.resolve_game();
    }

    fn get_observation(&self) -> Observation {
        Observation {
            // min_call: self.min_call, // Uncomment if you need to use it in the agent function
            // phase: self.phase, // Uncomment if you need to use it in the agent function
        }
    }

    fn step(&mut self, action: Action) {
        let player_id = self.current_player;
        let player = &mut self.players[player_id];
        if !player.active || player.all_in {
            self.next_player();
            return;
        }

        match action {
            Action::Fold => self.handle_fold(player_id),
            Action::Call => self.handle_call(player_id),
            Action::Raise => self.handle_raise(player_id),
        }

        if self.betting_round_over {
            self.advance_phase();
        }

        if !self.is_terminal() {
            self.next_player();
        }
    }

    fn is_terminal(&self) -> bool {
        let active_count = self.players.iter().filter(|p| p.active || p.all_in).count();
        active_count <= 1 || self.phase == Phase::Showdown
    }

    fn handle_fold(&mut self, player_id: usize) {
        let player = &mut self.players[player_id];
        player.active = false;
        self.pot += player.bet;
        player.bet = 0;
    }

    fn handle_call(&mut self, player_id: usize) {
        let player = &mut self.players[player_id];
        let call_amount = self.min_call - player.bet;
        let amount = std::cmp::min(call_amount, player.stack);
        player.bet += amount;
        player.stack -= amount;
        if player.stack == 0 {
            player.all_in = true;
        }
    }

    fn handle_raise(&mut self, player_id: usize) {
        let player = &mut self.players[player_id];
        let raise_amount = std::cmp::max(self.last_raise * 2, self.min_call * 2);
        let amount = std::cmp::min(raise_amount, player.stack);
        player.bet += amount;
        player.stack -= amount;
        self.min_call = player.bet;
        self.last_raise = amount;
        self.last_aggressor = Some(player_id);
        if player.stack == 0 {
            player.all_in = true;
        }
    }

    fn next_player(&mut self) {
        let start_player = self.current_player;
        loop {
            self.current_player = (self.current_player + 1) % self.num_players;
            if self.current_player == start_player {
                self.betting_round_over = true;
                break;
            }
            let player = &self.players[self.current_player];
            if player.active && !player.all_in {
                break;
            }
        }

        if let Some(last_aggressor) = self.last_aggressor {
            if self.current_player == last_aggressor {
                self.betting_round_over = true;
            }
        }
    }

    fn advance_phase(&mut self) {
        for player in &mut self.players {
            self.pot += player.bet;
            player.bet = 0;
        }
        self.min_call = 0;
        self.last_raise = 0;
        self.betting_round_over = false;
        self.last_aggressor = None;

        self.phase = match self.phase {
            Phase::PreFlop => {
                self.community_cards.push(self.deck.deal());
                self.community_cards.push(self.deck.deal());
                self.community_cards.push(self.deck.deal());
                Phase::Flop
            }
            Phase::Flop => {
                self.community_cards.push(self.deck.deal());
                Phase::Turn
            }
            Phase::Turn => {
                self.community_cards.push(self.deck.deal());
                Phase::River
            }
            Phase::River => Phase::Showdown,
            Phase::Showdown => Phase::Showdown,
        };

        self.current_player = (self.dealer_position + 1) % self.num_players;
        while {
            let player = &self.players[self.current_player];
            (!player.active || player.all_in) && self.current_player != self.dealer_position
        } {
            self.current_player = (self.current_player + 1) % self.num_players;
        }
    }

    fn resolve_game(&mut self) {
        let active_players: Vec<_> = self
            .players
            .iter()
            .enumerate()
            .filter(|(_, p)| p.active || p.all_in)
            .collect();

        if active_players.len() == 1 {
            let (winner_id, _) = active_players[0];
            let winner = &mut self.players[winner_id];
            winner.stack += self.pot;
            println!(
                "Player {} wins the pot of {} by default.",
                winner_id, self.pot
            );
            return;
        }

        let mut best_rank: Option<u32> = None;
        let mut winners = vec![];
        for (player_id, player) in active_players {
            let rank = HandEvaluator::evaluate_hand(&player.hand, &self.community_cards);
            if best_rank.is_none() || rank < best_rank.unwrap() {
                best_rank = Some(rank);
                winners.clear();
                winners.push(player_id);
            } else if rank == best_rank.unwrap() {
                winners.push(player_id);
            }
        }

        let split_pot = self.pot / winners.len() as i32;
        for &winner_id in &winners {
            let winner = &mut self.players[winner_id];
            winner.stack += split_pot;
            println!(
                "Player {} wins {} with hand rank {}.",
                winner_id,
                split_pot,
                best_rank.unwrap()
            );
        }
    }

    fn post_blind(&mut self, player_id: usize, amount: i32) {
        let player = &mut self.players[player_id];
        let blind_amount = std::cmp::min(amount, player.stack);
        player.bet += blind_amount;
        player.stack -= blind_amount;
        self.pot += blind_amount;
        self.min_call = std::cmp::max(self.min_call, player.bet);
        if player.stack == 0 {
            player.all_in = true;
        }
    }
}

// Random agent function
fn random_agent_function(_player_id: usize, _observation: &Observation) -> Action {
    let mut rng = thread_rng();
    match rng.gen_range(0..3) {
        0 => Action::Fold,
        1 => Action::Call,
        _ => Action::Raise,
    }
}

// Main function
fn main() {
    let agent_function = Box::new(random_agent_function);
    let mut env = PokerEnvironment::new(NUM_PLAYERS, agent_function);
    env.play_game();
    println!("Game simulation completed.");
}

is this code done?