use core::panic;
use std::fmt::Display;

use crate::error::*;
use crate::wlist::word::{Word, WordData};
use crate::wlist::WordList;

use libpt::log::{debug, trace};

pub mod response;
use response::GuessResponse;

pub mod evaluation;

pub mod summary;

use self::evaluation::Evaluation;
use self::response::Status;

#[derive(Debug, Clone, PartialEq)]
pub struct Game<'wl, WL>
where
    WL: WordList,
{
    length: usize,
    precompute: bool,
    max_steps: usize,
    step: usize,
    solution: Option<WordData>,
    wordlist: &'wl WL,
    responses: Vec<GuessResponse>,
    // TODO: keep track of the letters the user has tried
}

impl<'wl, WL: WordList> Game<'wl, WL> {
    /// get a new [`GameBuilder`]
    pub fn builder(wl: &'wl WL) -> GameBuilder<'wl, WL> {
        GameBuilder::new(wl, true)
    }
    /// Create a [Game] of wordle
    ///
    /// This method will load the wordlist, determine if a word may be used as a solution for a
    /// game, and select a solution at random.
    ///
    /// It will also set a few values to their initial state.
    ///
    /// Don't use this method directly, instead, make use of the [`GameBuilder`].
    ///
    /// # Errors
    ///
    /// No Errors
    ///
    /// # Parameters
    ///
    /// `length` - how many chars the solution has
    /// `precompute` -  how many chars the solution has
    /// `max_steps` -  how many tries the player has
    /// `precompute` -  how many chars the solution has
    /// `wlist` -  which wordlist to use
    /// `generate_solution` -  should the game have a randomly generated solution?

    pub fn build(
        length: usize,
        precompute: bool,
        max_steps: usize,
        wlist: &'wl WL,
        generate_solution: bool,
    ) -> GameResult<Self> {
        // TODO: check if the length is in the range bounds of the wordlist
        let game: Game<'wl, WL> = Game {
            length,
            precompute,
            max_steps,
            step: 0,
            solution: if generate_solution {
                Some(wlist.rand_solution())
            } else {
                None
            },
            wordlist: wlist,
            responses: Vec::new(),
        };

        Ok(game)
    }

    /// set a solution, can be used for testing
    pub fn set_solution(&mut self, sol: Option<WordData>) {
        self.solution = sol;
    }

    /// Make a new guess
    ///
    /// The word will be evaluated against the [solution](Game::solution) of the [Game].
    /// A [GuessResponse] will be formulated, showing us which letters are correctly placed, in the
    /// solution, or just wrong.
    ///
    /// Note that you do not need to use the [GuessResponse], it is appended to the game state.
    ///
    /// # Errors
    ///
    /// This function will return an error if the length of the [Word] is wrong It will also error
    /// if the game is finished.
    pub fn guess(&mut self, guess: &Word, eval: Option<Evaluation>) -> GameResult<GuessResponse> {
        if guess.len() != self.length {
            return Err(GameError::GuessHasWrongLength(guess.len()));
        }
        if self.finished() || self.step > self.max_steps {
            return Err(GameError::TryingToPlayAFinishedGame);
        }
        if self.wordlist.get_word(guess).is_none() {
            return Err(GameError::WordNotInWordlist(guess.to_string()));
        }
        self.step += 1;

        let response;
        if eval.is_some() && self.solution.is_none() {
            response = GuessResponse::new(guess, eval.unwrap(), self);
        } else if let Some(solution) = self.solution.clone() {
            response = GuessResponse::new(guess, Self::evaluate(solution, guess), self);
        } else {
            panic!("there is neither an evaluation nor a predefined solution for this guess");
        }
        self.responses.push(response.clone());
        Ok(response)
    }

    /// Generates an [Evaluation] for a given solution and guess.
    pub(crate) fn evaluate(solution: WordData, guess: &Word) -> Evaluation {
        let mut evaluation = Vec::new();
        let mut status: Status;
        let mut buf = solution.0.clone();
        for ((idx, c_guess), c_sol) in guess.chars().enumerate().zip(solution.0.chars()) {
            if c_guess == c_sol {
                status = Status::Matched;
                buf.replace_range(idx..idx + 1, "_");
            } else if buf.contains(c_guess) {
                status = Status::Exists;
                buf = buf.replacen(c_guess, "_", 1);
            } else {
                status = Status::None
            }
            evaluation.push((c_guess, status));
        }
        evaluation.into()
    }

    /// discard the last n responses
    pub fn undo(&mut self, n: usize) -> WResult<()> {
        self.responses
            .drain(self.responses.len() - n..self.responses.len());
        Ok(())
    }

    /// get how many characters the words have for this game
    pub fn length(&self) -> usize {
        self.length
    }

    /// get the solution for this game, if the game is aware of one.
    ///
    /// Consider that games may also be played on other platforms, so the game might not "know" the
    /// solution yet.
    pub fn solution(&self) -> Option<&WordData> {
        self.solution.as_ref()
    }

    /// get how many guesses have been made already
    pub fn step(&self) -> usize {
        self.step
    }

    /// true if the game has finished and no more guesses can be made
    pub fn finished(&self) -> bool {
        if self.responses().is_empty() {
            return false;
        }
        self.responses().last().unwrap().finished()
    }

    /// true if the game has finished and the solution was found
    pub fn won(&self) -> bool {
        if !self.finished() || self.responses().is_empty() {
            return false;
        }
        self.responses().last().unwrap().won()
    }

    /// get how many tries the player has
    pub fn max_steps(&self) -> usize {
        self.max_steps
    }

    /// get the responses that were already made
    pub fn responses(&self) -> &Vec<GuessResponse> {
        &self.responses
    }

    /// get the most recent response
    pub fn last_response(&self) -> Option<&GuessResponse> {
        self.responses().last()
    }

    /// get the [WordList] for this game
    pub fn wordlist(&self) -> &WL {
        self.wordlist
    }

    /// get the [Words](Word) that have already been tried
    pub(crate) fn made_guesses(&self) -> Vec<&Word> {
        self.responses.iter().map(|r| r.guess()).collect()
    }
}

/// Build and Configure a [`Game`]
///
/// This struct is used to build and configure a [`Game`] of Wordle.
///
/// ## Examples
///
/// [`GameBuilder`] implements [`Default`]. [`Game::builder`] uses [`GameBuilder::default`].
/// You don't need to set custom values if you accept the defaults.
///
/// ```
/// # use wordle_analyzer::game::*;
/// # use wordle_analyzer::wlist::builtin::BuiltinWList;
/// # use anyhow::Result;
/// # fn main() -> Result<()> {
/// let wl = BuiltinWList::default();
/// let game: Game<_> = GameBuilder::new(&wl, true)
///     .build()?;
/// # Ok(())
/// # }
/// ```
///
/// ```
/// # use wordle_analyzer::game::*;
/// # use wordle_analyzer::wlist::builtin::BuiltinWList;
/// # use anyhow::Result;
/// # fn main() -> Result<()> {
/// let wl = BuiltinWList::default();
/// let game: Game<_> = Game::builder(&wl)
///     .length(5)
///     .precompute(false)
///     .max_steps(6)
///     .build()?;
/// # Ok(())
/// # }
/// ```
///
#[derive(Debug, Clone, PartialEq)]
pub struct GameBuilder<'wl, WL: WordList> {
    length: usize,
    precompute: bool,
    max_steps: usize,
    wordlist: &'wl WL,
    generate_solution: bool,
    solution: Option<WordData>,
}

impl<'wl, WL: WordList> GameBuilder<'wl, WL> {
    /// make a new [GameBuilder]
    ///
    /// We need a [WordList], so provide one here.
    pub fn new(wl: &'wl WL, generate_solution: bool) -> Self {
        Self {
            length: super::DEFAULT_WORD_LENGTH,
            precompute: false,
            max_steps: super::DEFAULT_MAX_STEPS,
            wordlist: wl,
            generate_solution,
            solution: None,
        }
    }

    /// build a [`Game`] with the stored configuration
    pub fn build(&'wl self) -> GameResult<Game<'wl, WL>> {
        trace!("{:#?}", self);
        let mut game: Game<WL> = Game::build(
            self.length,
            self.precompute,
            self.max_steps,
            self.wordlist,
            self.generate_solution,
        )?;
        if self.solution.is_some() {
            game.set_solution(self.solution.clone())
        }
        Ok(game)
    }

    /// Should we pre compute all possible answers? This will make startup significantly more
    /// expensive, but reduce the computing time while playing.
    ///
    /// Default is [`false`]
    pub fn precompute(mut self, precompute: bool) -> Self {
        self.precompute = precompute;
        self
    }

    /// Set the length of words for the game
    ///
    /// Default is [`super::DEFAULT_WORD_LENGTH`]
    pub fn length(mut self, length: usize) -> Self {
        self.length = length;
        self
    }

    /// Set the amount of guesses per game
    ///
    /// Default is [`super::DEFAULT_MAX_STEPS`]
    pub fn max_steps(mut self, max_steps: usize) -> Self {
        self.max_steps = max_steps;
        debug!("max steps: {:#?}", self.max_steps);
        self
    }

    /// Set the wordlist for the builder
    ///
    /// The builder can be used multiple times. Each [`Game`] will have a immutable reference to
    /// `wl`.
    pub fn wordlist(mut self, wl: &'wl WL) -> Self {
        self.wordlist = wl;
        self
    }

    /// Enable or disable Generation of a solution for this builder
    ///
    /// Default is true
    pub fn generate_solution(mut self, generate: bool) -> Self {
        self.generate_solution = generate;
        self
    }

    /// Set the solution for the games built by the builder
    ///
    /// If this is [Some], then the solution generated by
    /// [generate_solution](Self::generate_solution) will be overwritten (if it
    /// is true).
    ///
    /// If [generate_solution](Self::generate_solution) is false and this method is not used, the
    /// game will not have a predetermined solution and will not be able to generate evaluations
    /// for guesses, so these will need to be added manually by the user. The intention is that
    /// this can be used for use cases where the user plays wordle not within wordle-analyzer but
    /// in another program (like their browser). It can also be used to test solvers.
    pub fn solution(mut self, solution: Option<WordData>) -> Self {
        self.solution = solution;
        self
    }
}

impl<'wl, WL: WordList> Display for Game<'wl, WL> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // TODO: make this actually useful
        // TODO: make this actually fancy
        write!(
            f,
            "turn:\t\t{}\nsolution:\t{:?}\nguesses:\t",
            self.step(),
            self.solution(),
        )?;
        for s in self
            .responses()
            .iter()
            .map(|v| v.evaluation().to_owned().colorized_display())
        {
            write!(f, "\"")?;
            for si in s {
                write!(f, "{si}")?;
            }
            write!(f, "\", ")?;
        }
        Ok(())
    }
}
