use uuid::Uuid;

use serde_cbor::{from_slice, to_vec};

use postgres::transaction::Transaction;

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

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

#[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(16);
        self
    }

    pub fn add_loss(&mut self) -> &mut Player {
        self.score.losses += 1;
        self.set_ready(false);
        self.vbox.balance_add(12);
        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 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_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;
            player_update(tx, p, false)
        },
        Err(_) => {
            return player_create(tx, Player::new(account.id, Uuid::nil(), &account.name, cryps), &account)
        }
    }
}
