use uuid::Uuid;

use serde_cbor::{from_slice, to_vec};

use postgres::transaction::Transaction;

use failure::Error;
use failure::err_msg;

use std::iter;

use rpc::{InstanceJoinParams, InstanceReadyParams};
use account::Account;
use player::{Player, Score, player_create, player_get, player_update};
use cryp::{Cryp, cryp_get};
use mob::{instance_mobs};
use game::{Game, Team, game_get, game_write, game_instance_new, game_instance_join, game_global_get, game_global_set};

#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
enum InstancePhase {
    Open,
    Vbox,
    Games,
    Finished,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct Round {
    player_ids: Vec<Uuid>,
    game_id: Uuid,
    finished: bool,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct Instance {
    id: Uuid,
    players: Vec<Player>,
    phase: InstancePhase,
    rounds: Vec<Vec<Round>>,
    open: bool,
    pve: bool,
}

impl Instance {
    fn new() -> Instance {
        Instance {
            id: Uuid::new_v4(),
            players: vec![],
            rounds: vec![],
            phase: InstancePhase::Open,
            open: true,
            pve: false,
        }
    }

    fn add_bots(mut self) -> Instance {
        self.pve = true;
        self.open = false;
        self.players = iter::repeat_with(|| {
                let bot_id = Uuid::new_v4();
                let cryps = instance_mobs(bot_id);
                Player::new(bot_id, self.id, &bot_id.to_string(), cryps).set_bot(true)
            })
            .take(15)
            .collect::<Vec<Player>>();
        self
    }

    fn add_player(&mut self, player: Player) -> &mut Instance {
        self.players.push(player);
        self
    }

    pub fn player_update(mut self, player: Player, ignore_phase: bool) -> Result<Instance, Error> {
        if !ignore_phase && self.phase != InstancePhase::Vbox {
            return Err(format_err!("instance not in vbox phase ({:?})", self.phase));
        }

        let i = self.players
            .iter()
            .position(|p| p.id == player.id)
            .ok_or(err_msg("player_id not found"))?;

        self.players[i] = player;

        Ok(self)
    }

    fn player_ready(&mut self, player: &mut Player) -> Result<&mut Instance, Error> {
        if self.phase != InstancePhase::Vbox {
            return Err(err_msg("instance not in vbox phase"));
        }

        let i = self.players
            .iter()
            .position(|p| p.id == player.id)
            .ok_or(err_msg("player_id not found"))?;

        player.set_ready(true);

        self.players[i] = player.clone();

        if self.vbox_phase_finished() {
            self.games_phase_start();
        }

        Ok(self)
    }

    fn bot_vs_player_game(&self, player: &Player) -> Result<Game, Error> {
        let current_round = self.current_round(player);
        let bot_id = current_round.player_ids.iter().find(|id| **id != player.id).unwrap();

        let plr = self.players.clone().into_iter().find(|p| p.id == player.id).unwrap();
        let bot = self.players.clone().into_iter().find(|p| p.id == *bot_id).unwrap();

        let mut game = Game::new();
        game.id = current_round.game_id;
        game
            .set_team_num(2)
            .set_team_size(3)
            .set_instance(self.id);

        // add the players
        let mut plr_team = Team::new(plr.account);
        plr_team.set_cryps(plr.cryps);

        let mut bot_team = Team::new(bot.account);
        bot_team.set_cryps(bot.cryps);
        bot_team.set_bot();

        game
            .team_add(plr_team)?
            .team_add(bot_team)?;

        game = game.start();

        Ok(game)
    }

    fn can_start(&self) -> bool {
        match self.pve {
            true => self.players.len() == 16,
            false => self.players.len() == 2,
        }
    }

    fn start(&mut self) -> &mut Instance {
        // self.players.sort_unstable_by_key(|p| p.id);
        self.open = false;
        self.vbox_phase_start()
    }

    fn vbox_phase_start(&mut self) -> &mut Instance {
        self.phase = InstancePhase::Vbox;
        self.generate_rounds();
        self.bot_vbox_phase();

        self
    }

    fn vbox_phase_finished(&self) -> bool {
        self.players.iter().all(|p| p.ready)
    }

    // requires no input
    // just do it
    fn games_phase_start(&mut self) -> &mut Instance {
        if self.phase != InstancePhase::Vbox {
            panic!("instance not in vbox phase");
        }
        assert!(self.vbox_phase_finished());

        self.phase = InstancePhase::Games;

        self.bot_games_phase();

        self
    }

    fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> {
        let round_num = self.rounds.len() - 1;
        self.rounds[round_num]
            .iter_mut()
            .find(|r| r.game_id == game.id)
            .ok_or(err_msg("could not find matchup in current round"))?
            .finished = true;

        Ok(self)
    }

    fn games_phase_finished(&self) -> bool {
        match self.rounds.last() {
            Some(r) => r.iter().all(|g| g.finished),
            None => true,
        }
    }

    fn bot_vbox_phase(&mut self) -> &mut Instance {
        for bot in self.players.iter_mut().filter(|p| p.bot) {
            bot.vbox.fill();

            // paths for what to do
            // use a spec
            // craft a spec
            // craft a skill
            // check for cryps w/ no skills
            // create a skill()
            //      find base
            //      pick 2 random colours

            bot.set_ready(true);
        }

        self
    }

    fn bot_games_phase(&mut self) -> &mut Instance {
        if self.phase != InstancePhase::Games {
            panic!("instance not in games phase");
        }

        if self.pve {
            let r = self.rounds.len() - 1;
            // println!("round num {:?}", r);
            // println!("{:?}", self.rounds[r]);
            for mut round in self.rounds[r].iter_mut() {
                if self.players
                    .iter()
                    .filter(|p| round.player_ids.contains(&p.id) && p.bot)
                    .count() == 2 {
                        // println!("should play a game between {:?}", round.player_ids);
                        let a = self.players.clone().into_iter().find(|p| p.id == round.player_ids[0]).unwrap();
                        let b = self.players.clone().into_iter().find(|p| p.id == round.player_ids[1]).unwrap();

                        // println!("{:?} vs {:?}", a.name, b.name);

                        let mut game = Game::new();
                        game
                            .set_team_num(2)
                            .set_team_size(3);

                        // add the players
                        let mut a_team = Team::new(a.account);
                        a_team.set_cryps(a.cryps);
                        a_team.set_bot();

                        let mut b_team = Team::new(b.account);
                        b_team.set_cryps(b.cryps);
                        b_team.set_bot();

                        game
                            .team_add(a_team).unwrap()
                            .team_add(b_team).unwrap();

                        game = game.start();

                        assert!(game.finished());
                        let winner = game.winner().unwrap();

                        round.finished = true;

                        for team in game.teams.iter() {
                            let mut player = self.players.iter_mut().find(|p| p.account == team.id).unwrap();
                            match team.id == winner.id {
                                true => player.add_win(),
                                false => player.add_loss(),
                            };
                        }

                    }
            }
        }

        self
    }

    fn generate_rounds(&mut self) -> &mut Instance {
        let round_num = self.rounds.len();
        let mut matched_players = self.players
            .iter()
            .map(|p| p.id)
            .collect::<Vec<Uuid>>();

        let np = matched_players.len();

        if round_num > 0 {
            matched_players.rotate_right(round_num % np);
            matched_players.swap(0,1);
        }

        // only set up for even player numbers atm
        // no byes
        let current_round = matched_players[0..(np / 2)]
            .iter()
            .enumerate()
            .map(|(i, id)| Round {
                player_ids: vec![*id, matched_players[np - (i + 1)]],
                game_id: Uuid::new_v4(),
                finished: false,
            })
            .collect::<Vec<Round>>();

        self.rounds.push(current_round);

        self
    }

    fn current_round(&self, player: &Player) -> &Round {
        let round_num = self.rounds.len() - 1;

        let current_round = self.rounds[round_num]
            .iter()
            .find(|g| g.player_ids.contains(&player.id))
            .unwrap();

        current_round
    }

    fn scores(&self) -> Vec<(String, Score)> {
        let mut scores = self.players.iter()
            .map(|p| (p.name.clone(), p.score))
            .collect::<Vec<(String, Score)>>();
        scores.sort_unstable_by_key(|s| s.1.wins);
        scores.reverse();

        scores
    }
}

pub fn instance_create(tx: &mut Transaction, instance: Instance) -> Result<Instance, Error> {
    let instance_bytes = to_vec(&instance)?;

    let query = "
        INSERT INTO instances (id, data, open)
        VALUES ($1, $2, $3)
        RETURNING id;
    ";

    let result = tx
        .query(query, &[&instance.id, &instance_bytes, &instance.open])?;

    result.iter().next().ok_or(format_err!("no instances written"))?;

    return Ok(instance);
}

pub fn instance_update(tx: &mut Transaction, instance: Instance) -> Result<Instance, Error> {
    let instance_bytes = to_vec(&instance)?;

    let query = "
        UPDATE instances
        SET data = $1, open = $2
        WHERE id = $3
        RETURNING id, data;
    ";

    let result = tx
        .query(query, &[&instance_bytes, &instance.open, &instance.id])?;

    result.iter().next().ok_or(err_msg("no instance row returned"))?;

    // println!("{:?} wrote instance", instance.id);

    return Ok(instance);
}

pub fn instance_get(tx: &mut Transaction, instance_id: Uuid) -> Result<Instance, Error> {
    let query = "
        SELECT *
        FROM instances
        WHERE id = $1;
    ";

    let result = tx
        .query(query, &[&instance_id])?;

    let returned = match result.iter().next() {
        Some(row) => row,
        None => return Err(err_msg("instance not found")),
    };

    let instance_bytes: Vec<u8> = returned.get("data");
    let instance = from_slice::<Instance>(&instance_bytes)?;

    return Ok(instance);
}

pub fn instance_get_open(tx: &mut Transaction) -> Result<Instance, Error> {
    let query = "
        SELECT *
        FROM instances
        WHERE open = true;
    ";

    let result = tx
        .query(query, &[])?;

    let returned = match result.iter().next() {
        Some(row) => row,
        None => return Err(err_msg("instance not found")),
    };

    let instance_bytes: Vec<u8> = returned.get("data");
    let instance = from_slice::<Instance>(&instance_bytes)?;

    return Ok(instance);
}


pub fn instance_join(params: InstanceJoinParams, tx: &mut Transaction, account: &Account) -> Result<Player, Error> {
    let mut instance = match params.pve {
        true => instance_create(tx, Instance::new().add_bots())?,
        false => match instance_get_open(tx) {
            Ok(i) => i,
            Err(_) => instance_create(tx, Instance::new())?,
        },
    };

    let cryps = params.cryp_ids
        .iter()
        .map(|id| cryp_get(tx, *id, account.id))
        .collect::<Result<Vec<Cryp>, Error>>()?;

    if cryps.len() != 3 {
        return Err(format_err!("incorrect team size. ({:})", 3));
    }

    let mut player = Player::new(account.id, instance.id, &account.name, cryps);
    player.vbox.fill();
    let player = player_create(tx, player, account)?;

    instance.add_player(player.clone());

    if instance.can_start() {
        instance.start();
    }

    instance_update(tx, instance)?;

    return Ok(player);
}

pub fn instance_ready_global(tx: &mut Transaction, _account: &Account, player: Player) -> Result<Game, Error> {
    // get the game
    let game = match game_global_get(tx) {
        Ok(g) => {
            println!("received global game {:?}", g.id);
            // if there is one try to join
            match game_instance_join(tx, player.clone(), g.id) {
                Ok(g) => g,
                // if fails make a new one
                Err(_e) => game_instance_new(tx, player, Uuid::new_v4())?,
            }
        },
        // if not found make a new one
        Err(_) => game_instance_new(tx, player, Uuid::new_v4())?,
    };

    // set the current game
    game_global_set(tx, &game)?;
    Ok(game)
}

pub fn instance_scores(params: InstanceReadyParams, tx: &mut Transaction, _account: &Account) -> Result<Vec<(String, Score)>, Error> {
    let scores = instance_get(tx, params.instance_id)?.scores();
    Ok(scores)
}

pub fn instance_ready(params: InstanceReadyParams, tx: &mut Transaction, account: &Account) -> Result<Game, Error> {
    let mut player = player_get(tx, account.id, params.instance_id)?;

    if params.instance_id == Uuid::nil() {
        return instance_ready_global(tx, account, player);
    }

    let mut instance = instance_get(tx, params.instance_id)?;

    // attempting to re-ready
    // send game state
    match instance.player_ready(&mut player) {
        Ok(_) => (),
        Err(_) => return game_get(tx, instance.current_round(&player).game_id),
    };

    let game_id = instance.current_round(&player).game_id;

    let game = match instance.pve {
        true => match game_get(tx, game_id) {
            Ok(g) => g,
            Err(_) => {
                let game = instance.bot_vs_player_game(&player)?;
                game_write(&game, tx)?;
                game
            },
        },
        false => match game_get(tx, game_id) {
            Ok(_g) => game_instance_join(tx, player.clone(), game_id)?,
            Err(_) => game_instance_new(tx, player.clone(), game_id)?,
        }
    };

    player_update(tx, player, false)?;
    instance_update(tx, instance)?;
    return Ok(game);
}

pub fn global_game_finished(tx: &mut Transaction, game: &Game) -> Result<(), Error> {
    let winner = game.winner().ok_or(err_msg("game not finished"))?;

    for team in game.teams.iter() {
        let mut player = player_get(tx, team.id, Uuid::nil())?;
        match team.id == winner.id {
            true => player.add_win(),
            false => player.add_loss(),
        };
        player.vbox.fill();
        player_update(tx, player, true)?;
    }

    Ok(())
}

pub fn instance_game_finished(tx: &mut Transaction, game: &Game, instance_id: Uuid) -> Result<(), Error> {
    // update scores
    let winner = game.winner().ok_or(err_msg("game not finished"))?;

    for team in game.teams.iter() {
        match team.bot {
            true => {
                let mut instance = instance_get(tx, instance_id)?;
                {
                    let mut player = instance.players.iter_mut().find(|p| p.account == team.id).unwrap();
                    match team.id == winner.id {
                        true => player.add_win(),
                        false => player.add_loss(),
                    };
                }
                instance_update(tx, instance)?;
            },
            false => {
                let mut player = player_get(tx, team.id, instance_id)?;
                match team.id == winner.id {
                    true => player.add_win(),
                    false => player.add_loss(),
                };
                player_update(tx, player, true)?;
            },
        }
    }

    // update instance and persist
    let mut instance = instance_get(tx, instance_id)?;
    instance.game_finished(game)?;
    let mut instance = instance_update(tx, instance)?;

    // now modify the players and write them all
    // each player update will also update the instance in db
    if instance.games_phase_finished() {
        instance.vbox_phase_start();
        let instance = instance_update(tx, instance)?;

        for player in instance.players
            .iter()
            .filter(|p| !p.bot) {
            let mut player = player_get(tx, player.account, instance_id)?;
            player.vbox.fill();
            player_update(tx, player, false)?;
        }
    }

    // println!("{:?}", instance_get(tx, instance_id)?);

    Ok(())
}

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

    #[test]
    fn instance_pve_test() {
        let mut instance = Instance::new().add_bots();

        let player_account = Uuid::new_v4();
        let cryps = instance_mobs(player_account);
        let mut player = Player::new(player_account, instance.id, &"test".to_string(), cryps).set_bot(true);

        instance.add_player(player.clone());
        assert!(instance.can_start());

        instance.start();
        assert_eq!(instance.rounds[0].len(), 8);

        instance.player_ready(&mut player).unwrap();

        assert!(instance.games_phase_finished());
        instance.vbox_phase_start();

        instance.player_ready(&mut player).unwrap();

        instance.vbox_phase_start();

        instance.player_ready(&mut player).unwrap();

        assert_eq!(instance.rounds.len(), 3);
    }
}