use uuid::Uuid;
use rand::prelude::*;

use serde_cbor::{from_slice, to_vec};

use postgres::transaction::Transaction;

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

use account::Account;
use cryp::{Cryp, Colours, cryp_get};
use vbox::{Vbox, Var, VarEffect};
use rpc::{PlayerStateParams, PlayerCrypsSetParams};
use instance::{instance_get, instance_update};

const DISCARD_COST: u16 = 5;

#[derive(Debug,Clone,Copy,Serialize,Deserialize)]
pub struct Score {
    pub wins: u8,
    pub losses: u8,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct Player {
    pub id: Uuid,
    pub instance: Uuid,
    pub account: Uuid,
    pub name: String,
    pub vbox: Vbox,
    pub score: Score,
    pub cryps: Vec<Cryp>,
    pub bot: bool,
    pub ready: bool,
}

impl Player {
    pub fn new(account: Uuid, instance: Uuid, name: &String, cryps: Vec<Cryp>) -> Player {
        Player {
            id: Uuid::new_v4(),
            account,
            instance,
            name: name.clone(),
            vbox: Vbox::new(account, instance),
            score: Score { wins: 0, losses: 0 },
            cryps,
            bot: false,
            ready: false,
        }
    }

    pub fn set_bot(mut self, bot: bool) -> Player {
        self.bot = bot;
        self
    }

    pub fn set_ready(&mut self, ready: bool) -> &mut Player {
        self.ready = ready;
        self
    }

    pub fn add_win(&mut self) -> &mut Player {
        self.score.wins += 1;
        self.set_ready(false);
        self.vbox.balance_add(12);
        self
    }

    pub fn add_loss(&mut self) -> &mut Player {
        self.score.losses += 1;
        self.set_ready(false);
        self.vbox.balance_add(9);
        self
    }

    pub fn cryp_get(&mut self, id: Uuid) -> Result<&mut Cryp, Error> {
        self.cryps.iter_mut().find(|c| c.id == id).ok_or(err_msg("cryp not found"))
    }

    pub fn autobuy(&mut self) -> &mut Player {
        let mut rng = thread_rng();

        // first check if any cryps have no skills
        // if there is one find an item in vbox that gives a skill
        while let Some(c) = self.cryps.iter().position(|c| c.skills.len() == 0) {
            if let Some(s) = self.vbox.bound.iter().position(|v| v.into_skill().is_some()) {
                let cryp_id = self.cryps[c].id;
                self.vbox_apply(s, cryp_id).expect("could not apply");
                continue;
            }
            println!("no skills available...");
        }

        // now keep buying and applying items cause whynot
        // inb4 montecarlo gan

        loop {
            let (target_cryp_i, target_cryp_id) = match self.cryps.iter().any(|c| c.skills.len() < 3) {
                true => {
                    let mut target_cryp_i = 0;
                    for (j, c) in self.cryps.iter().enumerate() {
                        if c.skills.len() < self.cryps[target_cryp_i].skills.len() {
                            target_cryp_i = j;
                        }
                    }
                    (target_cryp_i, self.cryps[target_cryp_i].id)
                },
                false => {
                    let i = rng.gen_range(0, 3);
                    (i, self.cryps[i].id)
                },
            };

            let needs_skills = self.cryps[target_cryp_i].skills.len() < 3;
            let group_i = match needs_skills {
                true => 1,
                false => 2,
            };


            let num_colours = self.vbox.bound
                .iter()
                .filter(|v| [Var::Red, Var::Green, Var::Blue].contains(v))
                .count();

            if self.vbox.bound.len() < 3 || num_colours < 2 {
                if (needs_skills && self.vbox.bits < 4) || self.vbox.bits < 5 {
                    // println!("insufficient balance");
                    break;
                }

                // get 2 colours and something else
                if self.vbox.free[0].len() < 2 {
                    break;
                }
                self.vbox_accept(0, 0).expect("could't accept colour 0");
                self.vbox_accept(0, 0).expect("could't accept colour 1");
                self.vbox_accept(group_i, 0).expect("could't accept group 0");
            }

            // println!("{:?}", self.vbox.bound);

            let skills = [Var::Attack, Var::Block, Var::Buff, Var::Debuff, Var::Stun];
            let combo_i = match group_i {
                1 => self.vbox.bound.iter().position(|v| skills.contains(v)).expect("no skill found"),
                2 => self.vbox.bound.iter().position(|v| v.into_spec().is_some()).expect("no spec found"),
                _ => panic!("unknown group_i"),
            };

            // first 2 colours can be whatever
            self.vbox_combine(vec![0, 1, combo_i]).ok();
            let var_i = self.vbox.bound.len() - 1;
            self.vbox_apply(var_i, target_cryp_id).ok();
        }

        return self;
    }

    pub fn vbox_discard(&mut self) -> Result<&mut Player, Error> {
        self.vbox.balance_sub(DISCARD_COST)?;
        self.vbox.fill();
        Ok(self)
    }

    pub fn vbox_accept(&mut self, group: usize, index: usize) -> Result<&mut Player, Error> {
        self.vbox.accept(group, index)?;
        Ok(self)
    }

    pub fn vbox_combine(&mut self, indices: Vec<usize>) -> Result<&mut Player, Error> {
        self.vbox.combine(indices)?;
        Ok(self)
    }

    pub fn vbox_reclaim(&mut self, index: usize) -> Result<&mut Player, Error> {
        self.vbox.reclaim(index)?;
        Ok(self)
    }

    pub fn vbox_apply(&mut self, index: usize, cryp_id: Uuid) -> Result<&mut Player, Error> {
        let var = self.vbox.bound.remove(index);

        match var.effect() {
            Some(VarEffect::Skill) => {
                let skill = var.into_skill().ok_or(format_err!("var {:?} has no associated skill", var))?;
                let cryp = self.cryp_get(cryp_id)?;
                // done here because i teach them a tonne of skills for tests
                let max_skills = 3;
                if cryp.skills.len() >= max_skills {
                    return Err(format_err!("cryp at max skills ({:?})", max_skills));
                }

                if cryp.knows(skill) {
                    return Err(format_err!("cryp already knows skill ({:?})" , skill));
                }

                cryp.learn_mut(skill);
            },
            Some(VarEffect::Spec) => {
                let spec = var.into_spec().ok_or(format_err!("var {:?} has no associated spec", var))?;
                let cryp = self.cryp_get(cryp_id)?;
                cryp.spec_add(spec)?;

            },
            None => return Err(err_msg("var has no effect on cryps")),
        }

        // now the var has been applied
        // recalculate the stats of the whole team
        let team_colours = self.cryps.iter().fold(Colours::new(), |tc, c| {
            Colours {
                red: tc.red + c.colours.red,
                green: tc.green + c.colours.green,
                blue: tc.blue + c.colours.blue
            }
        });

        for cryp in self.cryps.iter_mut() {
            cryp.apply_modifiers(&team_colours);
        }

        Ok(self)
    }

    pub fn vbox_unequip(&mut self, target: Var, cryp_id: Uuid) -> Result<&mut Player, Error> {
        if self.vbox.bound.len() >= 9 {
            return Err(err_msg("too many vars bound"));
        }

        match target.effect() {
            Some(VarEffect::Skill) => {
                let skill = target.into_skill().ok_or(format_err!("var {:?} has no associated skill", target))?;
                let cryp = self.cryp_get(cryp_id)?;
                cryp.forget(skill)?;
            },
            Some(VarEffect::Spec) => {
                let spec = target.into_spec().ok_or(format_err!("var {:?} has no associated spec", target))?;
                let cryp = self.cryp_get(cryp_id)?;
                cryp.spec_remove(spec)?;
            },
            None => return Err(err_msg("var has no effect on cryps")),
        }

        // now the var has been applied
        // recalculate the stats of the whole team
        let team_colours = self.cryps.iter().fold(Colours::new(), |tc, c| {
            Colours {
                red: tc.red + c.colours.red,
                green: tc.green + c.colours.green,
                blue: tc.blue + c.colours.blue
            }
        });

        for cryp in self.cryps.iter_mut() {
            cryp.apply_modifiers(&team_colours);
        }

        self.vbox.bound.push(target);
        self.vbox.bound.sort_unstable();

        Ok(self)
    }
}

pub fn player_get(tx: &mut Transaction, account_id: Uuid, instance_id: Uuid) -> Result<Player, Error> {
    let query = "
        SELECT *
        FROM players
        WHERE account = $1
        AND instance = $2;
    ";

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

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

    // tells from_slice to cast into a cryp
    let bytes: Vec<u8> = returned.get("data");
    let data = from_slice::<Player>(&bytes)?;

    return Ok(data);
}

pub fn player_create(tx: &mut Transaction, player: Player, account: &Account) -> Result<Player, Error> {
    let player_bytes = to_vec(&player)?;

    let query = "
        INSERT INTO players (id, instance, account, data)
        VALUES ($1, $2, $3, $4)
        RETURNING id, account;
    ";

    let result = tx
        .query(query, &[&player.id, &player.instance, &account.id, &player_bytes])?;

    let _returned = result.iter().next().expect("no row written");

    println!("wrote player {:} joined instance: {:}", account.name, player.instance);

    return Ok(player);
}

pub fn player_update(tx: &mut Transaction, mut player: Player, ignore_phase: bool) -> Result<Player, Error> {
    // sort vbox for niceness
    player.vbox.bound.sort_unstable();

    // update the instance this player is associated with
    // if not a global player
    if player.instance != Uuid::nil() {
        let instance = instance_get(tx, player.instance)?
            .player_update(player.clone(), ignore_phase)?;
        instance_update(tx, instance)?;
    }

    let bytes = to_vec(&player)?;

    let query = "
        UPDATE players
        SET data = $1
        WHERE id = $2
        RETURNING id, data;
    ";

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

    result.iter().next().ok_or(format_err!("player {:?} could not be written", player))?;

    Ok(player)
}

pub fn player_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> {
    let query = "
        DELETE
        FROM players
        WHERE id = $1;
    ";

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

    if result != 1 {
        return Err(format_err!("unable to delete player {:?}", id));
    }

    println!("player deleted {:?}", id);

    return Ok(());
}

pub fn player_state(params: PlayerStateParams, tx: &mut Transaction, account: &Account) -> Result<Player, Error> {
    player_get(tx, account.id, params.instance_id)
}

pub fn player_mm_cryps_set(params: PlayerCrypsSetParams, tx: &mut Transaction, account: &Account) -> Result<Player, Error> {
    if params.cryp_ids.len() != 3 {
        return Err(err_msg("team size is 3"));
    }

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

    match player_get(tx, account.id, Uuid::nil()) {
        Ok(mut p) => {
            p.cryps = cryps;
            p.vbox = Vbox::new(account.id, Uuid::nil());
            player_update(tx, p, false)
        },
        Err(_) => {
            return player_create(tx, Player::new(account.id, Uuid::nil(), &account.name, cryps), &account)
        }
    }
}

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

    #[test]
    fn player_bot_vbox_test() {
        let player_account = Uuid::new_v4();
        let cryps = instance_mobs(player_account);
        let mut player = Player::new(player_account, Uuid::new_v4(), &"test".to_string(), cryps).set_bot(true);

        player.autobuy();
        assert!(player.cryps.iter().all(|c| c.skills.len() == 2));
    }
}