//! Playable local modes for the terminal game.

pub mod endless;
pub mod line_race;
pub mod score_attack;

use crate::config::Config;
use crate::engine::board::Board;
use crate::engine::prng::generate_sequence;
use crate::input::live::LiveInput;
use crate::score_attack::score_board_locks;
use crate::sim::{Engine, EngineConfig};

/// Two minutes at 60 frames per second.
pub const SCORE_ATTACK_TIME_FRAMES: i32 = 7200;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Mode {
    Endless,
    LineRace,
    ScoreAttack,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Outcome {
    Running,
    Cleared,
    ToppedOut,
}

impl Mode {
    pub fn title(&self) -> &'static str {
        match self {
            Mode::Endless => " Endless ",
            Mode::LineRace => " Line Race ",
            Mode::ScoreAttack => " Score Attack ",
        }
    }

    pub fn engine_config(&self, config: &Config) -> EngineConfig {
        let handling = config.handling.clone();
        let allow_180 = config.allow_180;
        match self {
            Mode::Endless => endless::engine_config(handling, allow_180),
            Mode::LineRace => line_race::engine_config(handling, allow_180),
            Mode::ScoreAttack => score_attack::engine_config(handling, allow_180),
        }
    }
}

/// Computes the current end condition without mutating the game.
pub fn outcome_for(mode: Mode, topped_out: bool, lines: u32, frames: i32) -> Outcome {
    if topped_out {
        return Outcome::ToppedOut;
    }
    match mode {
        Mode::Endless => Outcome::Running,
        Mode::LineRace => {
            if lines >= line_race::GOAL_LINES {
                Outcome::Cleared
            } else {
                Outcome::Running
            }
        }
        Mode::ScoreAttack => {
            if frames >= SCORE_ATTACK_TIME_FRAMES {
                Outcome::Cleared
            } else {
                Outcome::Running
            }
        }
    }
}

#[derive(Clone, Copy, Debug)]
pub struct GameStats {
    pub lines: u32,
    pub pieces: u32,
    pub frames: i32,
    pub score: i64,
    pub pps: f64,
}

fn secs(frames: i32) -> f64 {
    frames as f64 / 60.0
}

/// Builds mode-specific stat lines for the live play side panel.
pub fn stat_lines_for(mode: Mode, s: &GameStats) -> Vec<String> {
    let mut lines = vec![
        format!("Pieces  {}", s.pieces),
        format!("PPS     {:.2}", s.pps),
    ];
    match mode {
        Mode::Endless => {
            lines.insert(0, format!("Score   {}", s.score));
            lines.insert(1, format!("Lines   {}", s.lines));
            lines.push(format!("Time    {:.1}s", secs(s.frames)));
        }
        Mode::LineRace => {
            lines.insert(0, format!("Lines   {}/40", s.lines));
            lines.push(format!("Time    {:.2}s", secs(s.frames)));
        }
        Mode::ScoreAttack => {
            let remaining = ((SCORE_ATTACK_TIME_FRAMES - s.frames).max(0)) as f64 / 60.0;
            lines.insert(0, format!("Score   {}", s.score));
            lines.insert(1, format!("Lines   {}", s.lines));
            lines.insert(2, format!("Level   {}", score_attack::level_for(s.lines)));
            lines.push(format!("Time Left {:.1}s", remaining));
        }
    }
    lines
}

/// Builds the pause-overlay result text after a mode ends.
pub fn summary_lines_for(mode: Mode, s: &GameStats, outcome: Outcome) -> Vec<String> {
    let header = match (mode, outcome) {
        (Mode::LineRace, Outcome::Cleared) => "Finished!".to_string(),
        (Mode::ScoreAttack, Outcome::Cleared) => "Time!".to_string(),
        _ => "Game Over".to_string(),
    };
    let mut lines = vec![header, String::new()];
    match mode {
        Mode::LineRace => {
            lines.push(format!("Time    {:.2}s", secs(s.frames)));
            lines.push(format!("Pieces  {}", s.pieces));
            lines.push(format!("PPS     {:.2}", s.pps));
        }
        Mode::ScoreAttack => {
            lines.push(format!("Score   {}", s.score));
            lines.push(format!("Lines   {}", s.lines));
            lines.push(format!("Level   {}", score_attack::level_for(s.lines)));
        }
        Mode::Endless => {
            lines.push(format!("Score   {}", s.score));
            lines.push(format!("Lines   {}", s.lines));
            lines.push(format!("Pieces  {}", s.pieces));
            lines.push(format!("Time    {:.1}s", secs(s.frames)));
            lines.push(format!("PPS     {:.2}", s.pps));
        }
    }
    lines.push(String::new());
    lines.push("R restart · Esc/M menu · Q quit".to_string());
    lines
}

pub struct Game {
    pub engine: Engine,
    pub input: LiveInput,
    mode: Mode,
    frames: i32,
}

impl Game {
    pub fn new(mode: Mode, config: &Config, seed: i64) -> Game {
        let board = Board::new(generate_sequence(seed, 4000, false));
        Game {
            engine: Engine::new(board, mode.engine_config(config)),
            input: LiveInput::with_keymap(config.keybinds.clone()),
            mode,
            frames: 0,
        }
    }

    pub fn mode(&self) -> Mode {
        self.mode
    }

    pub fn title(&self) -> &'static str {
        self.mode.title()
    }

    pub fn step(&mut self) {
        if self.engine.step(&mut self.input).is_some() {
            self.frames += 1;
        }
    }

    pub fn outcome(&self) -> Outcome {
        outcome_for(
            self.mode,
            self.engine.topped_out(),
            self.engine.board.lines_cleared,
            self.frames,
        )
    }

    pub fn stats(&self) -> GameStats {
        let board = &self.engine.board;
        let pps = if self.frames > 0 {
            board.pieces_placed as f64 / (self.frames as f64 / 60.0)
        } else {
            0.0
        };
        GameStats {
            lines: board.lines_cleared,
            pieces: board.pieces_placed,
            frames: self.frames,
            score: score_board_locks(board).score,
            pps,
        }
    }

    pub fn stat_lines(&self) -> Vec<String> {
        stat_lines_for(self.mode, &self.stats())
    }

    pub fn summary_lines(&self, outcome: Outcome) -> Vec<String> {
        summary_lines_for(self.mode, &self.stats(), outcome)
    }
}
