use actors::*;
use clap::Clap;
use cli::*;
use game::{Action, Controllable, Game, Tickable};
use graphics::standard_renderer;
use graphics::COLOR_BACKGROUND;
use log::{debug, info, trace};
use rand::SeedableRng;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use std::time::Duration;
use tokio::time::interval;

mod actors;
mod cli;
mod game;
mod graphics;
mod playfield;
mod random;
mod srs;
mod tetromino;

const TICKS_PER_SECOND: usize = 60;

#[tokio::main(core_threads = 16)]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let opts = crate::cli::Opts::parse();

    init_verbosity(&opts)?;

    let agent = match opts.subcmd {
        SubCommand::Play(_) => None,
        SubCommand::Train(sub_opts) => Some(match sub_opts.agent {
            Agent::QLearning => {
                qlearning::train_actor(qlearning::QLearningAgent::default(), &sub_opts)
            }
            Agent::ApproximateQLearning => {
                let agent =
                    qlearning::train_actor(qlearning::ApproximateQLearning::default(), &sub_opts);
                agent.dbg();
                agent
            }
            Agent::HeuristicGenetic => {
                let agent = genetic::train_actor(&sub_opts).await;
                agent.dbg();
                agent
            }
        }),
    };

    play_game(agent).await
}

async fn play_game(mut actor: Option<Box<dyn Actor>>) -> Result<(), Box<dyn std::error::Error>> {
    let mut rng = rand::rngs::SmallRng::from_entropy();
    let sdl_context = sdl2::init()?;
    let video_subsystem = sdl_context.video()?;
    let window = video_subsystem
        .window("retris", 800, 800)
        .position_centered()
        .build()?;
    let mut canvas = window.into_canvas().build()?;
    let mut event_pump = sdl_context.event_pump()?;
    let mut interval = interval(Duration::from_millis(1000 / TICKS_PER_SECOND as u64));

    'escape: loop {
        let mut game = Game::default();

        loop {
            match game.is_game_over() {
                Some(e) => {
                    println!("Lost due to: {:?}", e);
                    break;
                }
                None => (),
            }

            let cur_state = game.clone();

            // If there's an actor, the player action will get overridden. If not,
            // then then the player action falls through, if there is one. This is
            // to allow for restarting and quitting the game from the GUI.
            let mut action = None;
            for event in event_pump.poll_iter() {
                match event {
                    Event::Quit { .. }
                    | Event::KeyDown {
                        keycode: Some(Keycode::Escape),
                        ..
                    } => {
                        debug!("Escape registered");
                        break 'escape Ok(());
                    }
                    Event::KeyDown {
                        keycode: Some(Keycode::Left),
                        ..
                    } => {
                        debug!("Move left registered");
                        action = Some(Action::MoveLeft);
                    }
                    Event::KeyDown {
                        keycode: Some(Keycode::Right),
                        ..
                    } => {
                        debug!("Move right registered");
                        action = Some(Action::MoveRight);
                    }
                    Event::KeyDown {
                        keycode: Some(Keycode::Down),
                        ..
                    } => {
                        debug!("Soft drop registered");
                        action = Some(Action::SoftDrop);
                    }
                    Event::KeyDown {
                        keycode: Some(Keycode::Z),
                        ..
                    } => {
                        debug!("Rotate left registered");
                        action = Some(Action::RotateLeft);
                    }
                    Event::KeyDown {
                        keycode: Some(Keycode::X),
                        ..
                    } => {
                        debug!("Rotate right registered");
                        action = Some(Action::RotateRight);
                    }
                    Event::KeyDown {
                        keycode: Some(Keycode::Space),
                        ..
                    }
                    | Event::KeyDown {
                        keycode: Some(Keycode::Up),
                        ..
                    } => {
                        debug!("Hard drop registered");
                        action = Some(Action::HardDrop);
                    }
                    Event::KeyDown {
                        keycode: Some(Keycode::LShift),
                        ..
                    } => {
                        debug!("Hold registered");
                        action = Some(Action::Hold);
                    }
                    Event::KeyDown {
                        keycode: Some(Keycode::R),
                        ..
                    } => {
                        info!("Restarting game");
                        game = Game::default();
                    }
                    Event::KeyDown {
                        keycode: Some(e), ..
                    } => trace!("Ignoring keycode {}", e),
                    _ => (),
                }
            }

            actor.as_mut().map(|actor| {
                action =
                    Some(actor.get_action(&mut rng, &cur_state, &((&game).get_legal_actions())));
            });

            action.map(|action| match action {
                Action::Nothing => (),
                Action::MoveLeft => game.move_left(),
                Action::MoveRight => game.move_right(),
                Action::SoftDrop => game.move_down(),
                Action::HardDrop => game.hard_drop(),
                Action::Hold => game.hold(),
                Action::RotateLeft => game.rotate_left(),
                Action::RotateRight => game.rotate_right(),
            });

            game.tick();
            canvas.set_draw_color(COLOR_BACKGROUND);
            canvas.clear();
            standard_renderer::render(&mut canvas, &game);
            canvas.present();
            interval.tick().await;
        }

        info!("Final score: {}", game.score());
    }
}
