//! Coup is a game of deception for two to six players.

use rand::seq::SliceRandom;

use std::fmt;

use Action::*;
use Card::*;

pub type CoupResult<T> = Result<T, &'static str>;

#[repr(u8)]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
/// Each card represents the right to perform specific actions or counteractions.
pub enum Card {
    Duke,
    Assassin,
    Captain,
    Ambassador,
    Contessa,
}

impl Card {
    pub fn allows_action(self, action: Action) -> bool {
        matches!((self, action),
                 (_, Income) |
                 (_, ForeignAid) |
                 (Duke, Tax) |
                 (Assassin, Assassinate) |
                 (Ambassador, Exchange) |
                 (Captain, Steal))
    }
    pub fn blocks_action(self, action: Action) -> bool {
        matches!((self, action),
                 (Duke, ForeignAid) |
                 (Captain, Steal) |
                 (Ambassador, Steal) |
                 (Contessa, Assassinate))
    }
}

trait Stack {
    fn draw(&mut self, player: &mut Player) -> CoupResult<()>;
    fn draw_first(&mut self, card: Card) -> bool;
    fn shuffle(&mut self);
}

impl Stack for Vec<Card> {
    fn draw(&mut self, player: &mut Player) -> CoupResult<()> {
        match self.pop() {
            Some(card) => {
                player.cards.push(card);
                Ok(())
            },
            None => Err("Tried to draw from an empty deck!"),
        }
    }

    fn draw_first(&mut self, card: Card) -> bool {
        match self.iter().position(|c| *c == card) {
            Some(i) => {
                self.remove(i);
                true
            },
            None => false,
        }
    }
    
    fn shuffle(&mut self) {
        let deck = self.as_mut_slice();
        let mut rng = rand::thread_rng();
        deck.shuffle(&mut rng);
    }
}

impl fmt::Display for Card {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let name = match self {
            Duke => "Duke",
            Assassin => "Assassin",
            Captain => "Captain",
            Ambassador => "Ambassador",
            Contessa => "Contessa",
        };
        write!(f, "{}", name)
    }
}

#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Debug)]
/// The actions a player can perform on their turn.
pub enum Action {
    Income,
    ForeignAid,
    Coup,
    Tax,
    Assassinate,
    Exchange,
    Steal,
}

impl Action {
    /// If the action needs a target.
    pub fn is_targeted(self) -> bool {
        matches!(self, Coup | Steal | Assassinate)
    }

    /// Which players may challenge the action.
    pub fn challenger_mode(self) -> ResMode {
        match self {
            Income | ForeignAid | Coup => ResMode::None,
            Assassinate | Steal | Tax | Exchange => ResMode::Anyone,
        }
    }

    /// Which players may block the action.
    pub fn blocker_mode(self) -> ResMode {
        match self {
            Income | Tax | Exchange | Coup => ResMode::None,
            Assassinate | Steal => ResMode::Target,
            ForeignAid => ResMode::Anyone,
        }
    }

    /// How much the action costs to perform.
    pub fn coin_cost(self) -> u8 {
        match self {
            Assassinate => 3,
            Coup => 7,
            _ => 0,
        }
    }
}

impl fmt::Display for Action {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let name = match self {
            Income => "Income",
            ForeignAid => "Foreign Aid",
            Coup => "Coup",
            Tax => "Tax",
            Assassinate => "Assassinate",
            Exchange => "Exchange",
            Steal => "Steal",
        };
        write!(f, "{}", name)
    }
}

#[repr(u8)]
#[derive(Clone, Copy)]
/// How the other players may respond to an action.
pub enum ResMode {
    None,
    Target,
    Anyone,
}

#[derive(Clone)]
/// The cards and coins a single player possesses.
pub struct Player {
    pub coins: u8,
    pub cards: Vec<Card>,
}

impl Player {
    /// If the player still possesses any cards, and thus is still in the game
    pub fn is_alive(&self) -> bool {
        !self.cards.is_empty()
    }
    fn lose(&mut self, card: Card, deck: &mut Vec<Card>) {
        self.cards.draw_first(card);
        deck.push(card);
    }
    fn holds(&self, card: Card) -> bool {
        self.cards.iter().find(|&c| *c == card).is_some()
    }
}

impl Default for Player {
    fn default() -> Self {
        Player {
            cards: Vec::new(),
            coins: 2,
        }
    }
}

#[derive(Clone)]
pub struct Game {
    pub players: Vec<Player>,
    pub deck: Vec<Card>,
    pub discard: Vec<Card>,
    pub turn: usize,
}

macro_rules! thrice {
    ($($e:expr),*) => {
        vec![$($e, $e, $e),*]
    }
}

impl Game {
    pub fn new(num_players: usize) -> Self {
        let mut deck = thrice![Duke, Assassin, Captain, Ambassador, Contessa];
        deck.shuffle();
        let mut players = Vec::new();
        players.resize_with(num_players, Player::default);
        for player in &mut players {
            deck.draw(player).unwrap();
            deck.draw(player).unwrap();
        }
        Game {
            deck,
            players,
            discard: Vec::new(),
            turn: 0,
        }
    }
    pub fn is_game_over(&self) -> bool {
        self.players.iter().filter(|p| p.is_alive()).count() == 1
    }
    
    fn turn_iterator(&self) -> impl Iterator<Item = usize> + '_ {
        let players_turn_order = std::iter::successors(Some(self.turn), |p| {
            let next = (p + 1) % self.players.len();
            if next == self.turn {
                None
            } else {
                Some(next)
            }
        });
        players_turn_order.skip(1).filter(|p| self.players[*p].is_alive())
    }

    fn advance(&mut self) {
        let next = self.turn_iterator().next();
        if let Some(next) = next {
            self.turn = next;
        }
    }

    fn player_lose_influence(&mut self, id: usize, agents: &[&dyn Agent]) {
        let player = &mut self.players[id];
        let card = agents[id]
            .choose_lost_influence(&player.cards);
        player.lose(card, &mut self.discard);
    }

    pub fn block(&mut self, block: phase::Block, agents: &[&dyn Agent]) -> CoupResult<Phase> {
        match block.action.blocker_mode() {
            ResMode::None => Ok(Phase::Resolution(phase::Resolution {
                action: block.action,
                target: block.target,
            })),
            ResMode::Target => match agents[block.target.unwrap()].choose_block_card(&block) {
                Some(card) => {
                    if card.blocks_action(block.action) {
                        Ok(Phase::BlockChallenge(phase::BlockChallenge {
                            blocker: block.target.unwrap(),
                            block_card: card,
                            action: block.action,
                            target: block.target,
                        }))
                    } else {
                        Err("Card does not block action")
                    }
                },
                None => Ok(Phase::Resolution(phase::Resolution {
                    action: block.action,
                    target: block.target,
                })),
            }
            ResMode::Anyone => {
                for id in self.turn_iterator() {
                    if let Some(card) = agents[id].choose_block_card(&block) {
                        if card.blocks_action(block.action) {
                            return Ok(Phase::BlockChallenge(phase::BlockChallenge {
                                blocker: id,
                                block_card: card,
                                action: block.action,
                                target: block.target,
                            }))
                        } else {
                            return Err("Card does not block action")
                        }
                    }
                }
                Ok(Phase::Resolution(phase::Resolution {
                    action: block.action,
                    target: block.target,
                }))
            }
        }
    }

    pub fn block_challenge(&mut self, block_challenge: phase::BlockChallenge, agents: &[&dyn Agent]) -> CoupResult<Phase> {
        if agents[self.turn].should_block_challenge(&block_challenge) {
            if self.players[block_challenge.blocker].holds(block_challenge.block_card) {
                // Player challenged incorrectly, loses influence and turn is forfeit
                self.player_lose_influence(self.turn, agents);
                Ok(Phase::Done)
            } else {
                // Player challenged correctly, blocker loses a card
                self.player_lose_influence(block_challenge.blocker, agents);
                // Game continues
                Ok(Phase::Resolution(phase::Resolution {
                    action: block_challenge.action,
                    target: block_challenge.target,
                }))
            }
        } else {
            // Player chose not to challenge the block, turn is forfeit
            self.advance();
            Ok(Phase::Done)
        }   
    }

    pub fn resolution(&mut self, resolution: phase::Resolution, agents: &[&dyn Agent]) -> CoupResult<Phase> {
        let current_player = &mut self.players.get_mut(self.turn).unwrap();
        let current_agent = agents[self.turn];
        match resolution.action {
            Income => current_player.coins += 1,
            ForeignAid => current_player.coins += 2,
            Coup | Assassinate => match resolution.target {
                Some(target) => {
                    // Target may have died from challenge
                    let target_alive = self.players[target].is_alive();
                    if target_alive {
                        self.player_lose_influence(target, agents);
                    }
                }
                _ => return Err("Coup/Assassinate resolution has no target"),
            },
            Tax => current_player.coins += 3,
            Exchange => {
                let drawn = vec![self.deck.pop().unwrap(), self.deck.pop().unwrap()];
                let hand = current_player.cards.clone();
                let mut choices = [drawn, hand].concat();
                let discarded = current_agent.exchange(&choices);
                for card in discarded {
                    if !choices.draw_first(card) {
                        return Err("Exchanged a card that was not in choices");
                    } else {
                        self.deck.push(card);
                    }
                }
                current_player.cards = choices;
                self.deck.shuffle();
            }
            Steal => match resolution.target {
                Some(target) => {
                    let val = self.players[target].coins.min(2);
                    self.players[self.turn].coins += val;
                    self.players[target].coins -= val;
                    
                }
                _ => return Err("Steal resolution has no target"),
            }        
        }
        self.advance();
        Ok(Phase::Done)
    }
}

pub mod phase {
    //! Structures relating to game phases.
    //!
    //! Coup turns have 5 phases, depending on what actions are taken each phase
    //! some phases might be skipped. These structures marshal information
    //! between the phases.
    use super::{ Card, Action };
    #[derive(PartialEq, Debug)]
    pub enum Phase {
        Action(Action),
        //ActionChallenge(ActionChallenge),
        Block(Block),
        BlockChallenge(BlockChallenge),
        Resolution(Resolution),
        Done,
    }

    #[derive(PartialEq, Debug)]
    pub struct Block {
        pub action: Action,
        pub target: Option<usize>,
    }

    #[derive(PartialEq, Debug)]
    pub struct BlockChallenge {
        pub blocker: usize,
        pub block_card: Card,
        pub action: Action,
        pub target: Option<usize>,
    }

    #[derive(PartialEq, Debug)]
    pub struct Resolution {
        pub action: Action,
        pub target: Option<usize>,
    }
}

use phase::Phase;

/// An interface to a game to make strategic decisions.
pub trait Agent : fmt::Debug {
    /// Which [card](Card) the agent wishes to use to block the current action.
    /// If the agent does not wish to block, it should return [None]
    fn choose_block_card(&self, block: &phase::Block) -> Option<Card>;
    /// Should the agent challenge the block?
    fn should_block_challenge(&self, block_challenge: &phase::BlockChallenge) -> bool;
    /// The [Ambassador]'s exchange.
    /// Given 3 or 4 [Cards](Card) the agent must return two cards to the deck.
    fn exchange(&self, cards: &[Card]) -> [Card; 2];
    /// The player has lost influence, and must choose a [Card] from their hand
    /// to discard.
    fn choose_lost_influence(&self, cards: &[Card]) -> Card;
}

#[cfg(test)]
mod test {
    use super::*;

    macro_rules! make_dummy_agent {
        ($cbc: expr, $sbc: expr, $e: expr, $cli: expr) => {{
            #[derive(Debug)]
            struct DummyAgent;
            impl Agent for DummyAgent {
                #[allow(unused)]
                fn choose_block_card(&self, block: &phase::Block) -> Option<Card> {
                    $cbc
                }
                #[allow(unused)]
                fn should_block_challenge(&self, block_challenge: &phase::BlockChallenge) -> bool {
                    $sbc
                }
                #[allow(unused)]
                fn exchange(&self, cards: &[Card]) -> [Card; 2] {
                    $e
                }
                #[allow(unused)]
                fn choose_lost_influence(&self, cards: &[Card]) -> Card {
                    $cli
                }
            }
            DummyAgent
        }}
    }

    #[test]
    fn test_block() {
        let non_blocking_agent = make_dummy_agent!(None, unimplemented!(), unimplemented!(), unimplemented!());
        let ambassador_block_agent = make_dummy_agent!(Some(Ambassador), unimplemented!(), unimplemented!(), unimplemented!());
        let duke_block_agent = make_dummy_agent!(Some(Duke), unimplemented!(), unimplemented!(), unimplemented!());
        
        let deck = vec![];
        let discard = vec![];
        let players = vec![
            Player { coins: 2, cards: vec![Duke, Captain] },
            Player { coins: 2, cards: vec![Contessa] },
            Player { coins: 2, cards: vec![Ambassador] },
        ];
        let game = Game {
            deck,
            discard,
            players,
            turn: 0
        };


        // Test unblockable
        {
            let mut game = game.clone();
            assert_eq!(
                game.block(phase::Block {
                    action: Income,
                    target: None,
                }, &[&non_blocking_agent, &ambassador_block_agent, &ambassador_block_agent]),
                Ok(Phase::Resolution(phase::Resolution {
                    action: Income,
                    target: None
                }))
            );
        }

        // Test target blocked
        {
            let mut game = game.clone();
            assert_eq!(
                game.block(phase::Block {
                    action: Steal,
                    target: Some(2),
                }, &[&non_blocking_agent, &ambassador_block_agent, &ambassador_block_agent]),
                Ok(Phase::BlockChallenge(phase::BlockChallenge {
                    blocker: 2,
                    block_card: Ambassador,
                    action: Steal,
                    target: Some(2),
                }))
            );
        }

        // Test target non-blocked
        {
            let mut game = game.clone();
            assert_eq!(
                game.block(phase::Block {
                    action: Steal,
                    target: Some(2),
                }, &[&non_blocking_agent, &ambassador_block_agent, &non_blocking_agent]),
                Ok(Phase::Resolution(phase::Resolution {
                    action: Steal,
                    target: Some(2),
                }))
            );
        }

        // Test anyone blocked
        {
            let mut game = game.clone();
            assert_eq!(
                game.block(phase::Block {
                    action: ForeignAid,
                    target: None,
                }, &[&non_blocking_agent, &non_blocking_agent, &duke_block_agent]),
                Ok(Phase::BlockChallenge(phase::BlockChallenge {
                    blocker: 2,
                    block_card: Duke,
                    action: ForeignAid,
                    target: None,
                }))
            );
        }

        // Test no one blocked
        {
            let mut game = game.clone();
            assert_eq!(
                game.block(phase::Block {
                    action: ForeignAid,
                    target: None,
                }, &[&non_blocking_agent, &non_blocking_agent, &non_blocking_agent]),
                Ok(Phase::Resolution(phase::Resolution {
                    action: ForeignAid,
                    target: None,
                }))
            );
        }

        // Test bad block card
        {
            let mut game = game.clone();
            assert!(
                game.block(phase::Block {
                    action: ForeignAid,
                    target: None,
                }, &[&non_blocking_agent, &ambassador_block_agent, &non_blocking_agent])
                    .is_err()
            );
        }

    }

    #[test]
    fn test_block_challenge() {
        let challenge_agent = make_dummy_agent!(unimplemented!(), true, unimplemented!(), Assassin);
        let non_challenge_agent = make_dummy_agent!(unimplemented!(), false, unimplemented!(), Assassin);
        let block_agent = make_dummy_agent!(unimplemented!(), unimplemented!(), unimplemented!(), Contessa);
        let deck = vec![];
        let discard = vec![];
        let players = vec![
            Player { coins: 2, cards: vec![Assassin] },
            Player { coins: 2, cards: vec![Contessa] },
        ];
        let game = Game {
            deck,
            discard,
            players,
            turn: 0
        };

        // Test non-challenge
        {
            let mut game = game.clone();
            assert_eq!(
                game.block_challenge( phase::BlockChallenge {
                    blocker: 1,
                    block_card: Contessa,
                    action: Assassinate,
                    target: Some(1),
                }, &[&non_challenge_agent, &block_agent]),
                Ok(Phase::Done)
            );
            assert!(game.discard.is_empty());
        }

        // Test failed challenge
        {
            let mut game = game.clone();
            assert_eq!(
                game.block_challenge( phase::BlockChallenge {
                    blocker: 1,
                    block_card: Contessa,
                    action: Assassinate,
                    target: Some(1),
                }, &[&challenge_agent, &block_agent]),
                Ok(Phase::Done)
            );
            assert!(!game.discard.is_empty());
        }

          // Test successful challenge
        {
            let mut game = game.clone();
            assert_eq!(
                game.block_challenge( phase::BlockChallenge {
                    blocker: 1,
                    block_card: Duke,
                    action: ForeignAid,
                    target: None,
                }, &[&challenge_agent, &block_agent]),
                Ok(Phase::Resolution(phase::Resolution {
                    action: ForeignAid,
                    target: None,
                }))
            );
            assert!(!game.discard.is_empty());
        }
        
    }
        
    #[test]
    fn test_resolution() {
        let dummy_agent = make_dummy_agent!(unimplemented!(), unimplemented!(), [Contessa, Duke], Captain);
        let deck = vec![Contessa, Contessa];
        let discard = vec![];
        let players = vec![
            Player { coins: 2, cards: vec![Duke, Assassin] },
            Player { coins: 1, cards: vec![Captain] },
        ];
        let game = Game {
            deck,
            discard,
            players,
            turn: 0,
        };

        // Test income
        {
            let mut game = game.clone();
            game.resolution(phase::Resolution {
                action: Income,
                target: None,
            }, &[&dummy_agent, &dummy_agent]).unwrap();
            assert_eq!(game.players[0].coins, 3);
        }

        // Test foreign aid
        {
            let mut game = game.clone();
            game.resolution(phase::Resolution {
                action: ForeignAid,
                target: None,
            }, &[&dummy_agent, &dummy_agent]).unwrap();
            assert_eq!(game.players[0].coins, 4);
        }

        // Test coup / assassinate
        {
            let mut game = game.clone();
            game.resolution(phase::Resolution {
                action: Coup,
                target: Some(1),
            }, &[&dummy_agent, &dummy_agent]).unwrap();
            assert!(game.players[1].cards.is_empty());
            assert_eq!(game.discard, vec![Captain]);
        }

        // Test steal
        {
            let mut game = game.clone();
            game.resolution(phase::Resolution {
                action: Steal,
                target: Some(1),
            }, &[&dummy_agent, &dummy_agent]).unwrap();
            assert_eq!(game.players[0].coins, 3);
            assert_eq!(game.players[1].coins, 0);
        }

        // Test exchange
        {
            let mut game = game.clone();
            game.resolution(phase::Resolution {
                action: Exchange,
                target: Some(1),
            }, &[&dummy_agent, &dummy_agent]).unwrap();
            game.players[0].cards.sort();
            assert_eq!(game.players[0].cards, vec![Assassin, Contessa]);
            game.deck.sort();
            assert_eq!(game.deck, vec![Duke, Contessa]);
            assert!(game.discard.is_empty());
        }
    }
}