//! 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 wins_challenge(&self, action: Action) -> bool {
        self.cards.iter().find(|&c| c.allows_action(action)).is_some()
    }
            
    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 {
    /// Creates a new game, with default [Players](Player) and a shuffled deck of [Cards](card)
    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,
        }
    }
    /// If all but one [Players](Player) have lost all influence.
    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]) -> CoupResult<()>{
        let player = &mut self.players[id];
        let card = agents[id]
            .choose_lost_influence(&player.cards);
        if player.holds(card) {
            player.lose(card, &mut self.discard);
            Ok(())
        } else {
            Err("Player discarded a card they don't hold")
        }
    }

    pub fn action_challenge(&mut self, action_challenge: phase::ActionChallenge, agents: &[&dyn Agent]) -> CoupResult<Phase> {
        let block = Phase::Block(phase::Block {
            action: action_challenge.action,
            target: action_challenge.target,
        });
        match action_challenge.action.challenger_mode() {
            ResMode::None => Ok(block),
            ResMode::Target => unreachable!(),
            ResMode::Anyone => {
                let challenger = self.turn_iterator().find(|&id| agents[id].should_action_challenge(&action_challenge));
                if let Some(challenger) = challenger{
                    let current_player_wins = self.players[self.turn].wins_challenge(action_challenge.action);
                    if current_player_wins {
                        // Challenger loses influence
                        self.player_lose_influence(challenger, agents)?;
                        Ok(block)
                    } else {
                        // Player loses influence
                        self.player_lose_influence(self.turn, agents)?;
                        // Turn is forfeit
                        Ok(Phase::Done)
                    }
                } else {
                    Ok(block)
                }
            }
        }
    }

    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 ActionChallenge {
        pub action: Action,
        pub target: Option<usize>,
    }

    #[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 {
    /// Should the agent challenge the action?
    fn should_action_challenge(&self, action_challenge: &phase::ActionChallenge) -> bool;
    /// 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;
}

mod test;