// File: poker_environment.rs

use rand::prelude::*;
use itertools::Itertools; // Import Itertools for combinations
use std::fmt;
use std::fs::File;
use std::io::Write;

// 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, // 0-12 (2-Ace)
    suit: usize, // 0-3 (Hearts, Diamonds, Clubs, Spades)
}

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 {
        // Concatenate the hand and community cards
        let mut all_cards = hand.to_vec();
        all_cards.extend_from_slice(community_cards);

        // Map cards to integers
        let card_ints: Vec<u8> = all_cards.iter().map(|card| Self::card_to_int(*card)).collect();

        // Use a simple evaluator for the hand
        Self::evaluate_7_card_hand(&card_ints)
    }

    // Map a card to its unique integer representation
    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]; // Using NUM_SUITS

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

        (rank_prime as u8) | suit_bit
    }

    // Evaluate a 7-card hand
    fn evaluate_7_card_hand(cards: &[u8]) -> u32 {
        let mut best_rank = u32::MAX;

        // Generate all combinations of 5 cards out of 7
        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 {
        // Simple hand evaluator using counts of ranks and suits
        let mut rank_counts = [0u8; NUM_RANKS];
        let mut suit_counts = [0u8; NUM_SUITS];

        for &&card in cards {
            // Extract rank and suit from card integer
            let rank_prime = (card & 0x0F) as usize;
            let suit_bit = card & 0xF0;

            // Map rank prime back to rank index
            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;

            // Map suit bit back to suit index
            let suit_index = match suit_bit {
                0x10 => 0,
                0x20 => 1,
                0x40 => 2,
                0x80 => 3,
                _ => continue,
            };
            suit_counts[suit_index] += 1;
        }

        // Determine hand strength
        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)); // Sort counts descending

        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;
        // Handle Ace-low straight
        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 {
    id: usize,
    stack: i32,
    bet: i32,
    active: bool,
    all_in: bool,
    hand: Vec<Card>,
}

impl Player {
    fn new(id: usize, stack: i32) -> Player {
        Player {
            id,
            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();
    }
}

// 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, &str) -> Action + 'a>,
    log_file: Option<File>,
}

#[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, &str) -> 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,
            log_file: None,
        };
        env.initialize_game();
        env
    }

    fn initialize_game(&mut self) {
        // Initialize logging
        self.log_file = Some(File::create("game_log.txt").expect("Unable to create log file"));
        self.log_game_state("Game started");

        // Initialize players
        self.players.clear();
        for i in 0..self.num_players {
            self.players.push(Player::new(i, MAX_STACK));
        }
        // Initialize deck
        self.deck.reset();
        // Deal hands
        let mut log_messages = vec![]; // Collect log messages
        for player in &mut self.players {
            player.reset();
            player.hand.push(self.deck.deal());
            player.hand.push(self.deck.deal());
            // Collect log messages
            log_messages.push(format!(
                "Player {} dealt hand: {} {}",
                player.id, player.hand[0], player.hand[1]
            ));
        }
        // Write log messages after the loop
        for message in log_messages {
            self.log_game_state(&message);
        }
        // Blinds
        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;

        // Post blinds
        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(player_id);
                let action = (self.agent_function)(player_id, &observation);
                self.step(action);
            } else {
                self.next_player();
            }
        }
        self.resolve_game();
        // Close log file
        self.log_file = None;
    }

    fn get_observation(&self, player_id: usize) -> String {
        let player = &self.players[player_id];
        let mut obs = String::new();
        obs.push_str(&format!("PlayerID: {}\n", player_id));
        obs.push_str("Hand: ");
        for &card in &player.hand {
            obs.push_str(&format!("{} ", card));
        }
        obs.push_str("\nCommunity Cards: ");
        for &card in &self.community_cards {
            obs.push_str(&format!("{} ", card));
        }
        obs.push_str(&format!(
            "\nPot: {}\nCurrent Bet: {}\nMin Call: {}\nStack: {}\nPhase: {:?}\n",
            self.pot, player.bet, self.min_call, player.stack, self.phase
        ));
        obs
    }

    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);
                self.log_action(player_id, "FOLD");
            }
            Action::Call => {
                self.handle_call(player_id);
                self.log_action(player_id, "CALL");
            }
            Action::Raise => {
                self.handle_raise(player_id);
                self.log_action(player_id, "RAISE");
            }
        }

        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) {
        // Collect bets into the pot
        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;

        // Advance phase
        self.phase = match self.phase {
            Phase::PreFlop => {
                // Deal three community cards
                self.community_cards.push(self.deck.deal());
                self.community_cards.push(self.deck.deal());
                self.community_cards.push(self.deck.deal());
                self.log_game_state("Dealt Flop");
                Phase::Flop
            }
            Phase::Flop => {
                self.community_cards.push(self.deck.deal());
                self.log_game_state("Dealt Turn");
                Phase::Turn
            }
            Phase::Turn => {
                self.community_cards.push(self.deck.deal());
                self.log_game_state("Dealt River");
                Phase::River
            }
            Phase::River => Phase::Showdown,
            Phase::Showdown => Phase::Showdown,
        };

        self.log_game_state(&format!("Phase advanced to {:?}", self.phase));

        // Reset current player to the first active player
        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) {
        // Determine winner(s)
        let active_players: Vec<_> = self
            .players
            .iter()
            .enumerate()
            .filter(|(_, p)| p.active || p.all_in)
            .collect();

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

        // Showdown
        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;
            self.log_game_state(&format!(
                "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;
        }
    }

    fn log_game_state(&mut self, message: &str) {
        if let Some(file) = &mut self.log_file {
            writeln!(file, "Game State: {}", message).expect("Unable to write to log file");
        }
    }

    fn log_action(&mut self, player_id: usize, action: &str) {
        if let Some(file) = &mut self.log_file {
            writeln!(file, "Player {}: {}", player_id, action)
                .expect("Unable to write to log file");
        }
    }
}

// Example agent function
fn example_agent_function(_player_id: usize, observation: &str) -> Action {
    // Simple agent: Randomly choose between Call and Raise if stack allows
    let mut rng = thread_rng();
    let choice: f64 = rng.gen();
    if observation.contains("Min Call: 0") {
        if choice < 0.5 {
            Action::Call // Check
        } else {
            Action::Raise
        }
    } else if !observation.contains("RAISE") {
        if choice < 0.5 {
            Action::Call // Call
        } else {
            Action::Raise
        }
    } else {
        Action::Fold
    }
}

// Main function
fn main() {
    let agent_function = Box::new(example_agent_function);
    let mut env = PokerEnvironment::new(NUM_PLAYERS, agent_function);
    env.play_game();
    println!("Game simulation completed. See 'game_log.txt' for details.");
}

do not represent observation/actions as strings