use anyhow::{Context, Result};
use serde::{Deserialize, Serialize, de::DeserializeOwned};

use crate::world::Vec2;

pub const PROTOCOL_VERSION: u16 = 1;
pub const DEFAULT_BIND: &str = "0.0.0.0:4000";
pub const DEFAULT_SERVER: &str = "127.0.0.1:4000";
pub const MAX_PACKET_SIZE: usize = 16 * 1024;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ClientPacket {
    ClientHello {
        protocol_version: u16,
        name: String,
    },
    ClientInput {
        protocol_version: u16,
        seq: u32,
        client_tick: u64,
        buttons: u16,
        turn: i8,
        fire_seq: u32,
    },
    Ping {
        protocol_version: u16,
        nonce: u32,
        client_time_ms: u64,
    },
    Disconnect {
        protocol_version: u16,
    },
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ServerPacket {
    ServerWelcome {
        protocol_version: u16,
        accepted: bool,
        player_id: u8,
        map_text: String,
        server_tick: u64,
        initial: Option<PlayerSnapshot>,
        message: String,
    },
    ServerSnapshot {
        protocol_version: u16,
        server_tick: u64,
        players: Vec<PlayerSnapshot>,
        events: Vec<GameEvent>,
    },
    Pong {
        protocol_version: u16,
        nonce: u32,
        client_time_ms: u64,
        server_tick: u64,
    },
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PlayerSnapshot {
    pub id: u8,
    pub name: String,
    pub position: Vec2,
    pub angle: f32,
    pub health: i16,
    pub score: u32,
    pub deaths: u32,
    pub alive: bool,
    pub respawn_ms: u16,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum GameEvent {
    Hit {
        shooter: u8,
        target: u8,
        damage: i16,
    },
    Kill {
        shooter: u8,
        target: u8,
    },
    Joined {
        player: u8,
        name: String,
    },
    Left {
        player: u8,
    },
}

pub fn encode<T: Serialize>(value: &T) -> Result<Vec<u8>> {
    bincode::serde::encode_to_vec(value, bincode::config::standard())
        .context("failed to encode packet")
}

pub fn decode<T: DeserializeOwned>(bytes: &[u8]) -> Result<T> {
    let (value, _): (T, usize) =
        bincode::serde::decode_from_slice(bytes, bincode::config::standard())
            .context("failed to decode packet")?;
    Ok(value)
}

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

    #[test]
    fn client_packet_roundtrip() {
        let packet = ClientPacket::ClientInput {
            protocol_version: PROTOCOL_VERSION,
            seq: 7,
            client_tick: 11,
            buttons: 3,
            turn: -1,
            fire_seq: 2,
        };
        let bytes = encode(&packet).unwrap();
        let decoded: ClientPacket = decode(&bytes).unwrap();
        assert_eq!(packet, decoded);
    }

    #[test]
    fn server_packet_roundtrip() {
        let packet = ServerPacket::ServerSnapshot {
            protocol_version: PROTOCOL_VERSION,
            server_tick: 9,
            players: vec![PlayerSnapshot {
                id: 1,
                name: "A".to_owned(),
                position: Vec2::new(1.0, 2.0),
                angle: 0.5,
                health: 66,
                score: 3,
                deaths: 1,
                alive: true,
                respawn_ms: 0,
            }],
            events: vec![GameEvent::Hit {
                shooter: 0,
                target: 1,
                damage: 34,
            }],
        };
        let bytes = encode(&packet).unwrap();
        let decoded: ServerPacket = decode(&bytes).unwrap();
        assert_eq!(packet, decoded);
    }
}
