use derive_more::derive::Display;
use serde::{de, Deserialize, Serialize};

#[derive(Debug, Display, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum SystemPacket {
    Ping,
    Pong,
    Close { reason: String },
}

#[derive(Debug, Display, Clone, Deserialize, Serialize)]
pub enum SimConnectPacket {
    AtcID(String),
    Airplane(Airplane),
    Fuel(Fuel),
    Gps(Gps),
    OnGround(OnGround),
    IsParked(IsParked),
}

#[derive(Debug, Clone, Display, Deserialize, Serialize)]
#[display("[{atc_type} - {atc_model}] {title} ({category})")]
pub struct Airplane {
    pub atc_type: String,
    pub atc_model: String,
    pub title: String,
    pub category: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Fuel {
    pub center_quantity: f64,
    pub center_capacity: f64,

    pub center2_quantity: f64,
    pub center2_capacity: f64,

    pub center3_quantity: f64,
    pub center3_capacity: f64,

    pub left_main_quantity: f64,
    pub left_main_capacity: f64,

    pub left_aux_quantity: f64,
    pub left_aux_capacity: f64,

    pub left_tip_quantity: f64,
    pub left_tip_capacity: f64,

    pub right_main_quantity: f64,
    pub right_main_capacity: f64,

    pub right_aux_quantity: f64,
    pub right_aux_capacity: f64,

    pub right_tip_quantity: f64,
    pub right_tip_capacity: f64,

    pub external1_quantity: f64,
    pub external1_capacity: f64,

    pub external2_quantity: f64,
    pub external2_capacity: f64,

    pub weight: f64,
}

impl Fuel {
    pub fn total_quantity(&self) -> f64 {
        self.center_quantity
            + self.center2_quantity
            + self.center3_quantity
            + self.left_main_quantity
            + self.left_aux_quantity
            + self.left_tip_quantity
            + self.right_main_quantity
            + self.right_aux_quantity
            + self.right_tip_quantity
            + self.external1_quantity
            + self.external2_quantity
    }

    pub fn total_capacity(&self) -> f64 {
        self.center_capacity
            + self.center2_capacity
            + self.center3_capacity
            + self.left_main_capacity
            + self.left_aux_capacity
            + self.left_tip_capacity
            + self.right_main_capacity
            + self.right_aux_capacity
            + self.right_tip_capacity
            + self.external1_capacity
            + self.external2_capacity
    }
}

impl core::fmt::Display for Fuel {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let percent = (100.0 / self.total_capacity()) * self.total_quantity();

        write!(
            f,
            "{}GL/{}GL ({}%)",
            self.total_quantity(),
            self.total_capacity(),
            percent
        )
    }
}

#[derive(Debug, Clone, Display, Deserialize, Serialize)]
#[display("{lat}, {lon} at {alt}ft")]
pub struct Gps {
    pub lat: f64,
    pub lon: f64,
    pub alt: f64,
}

#[derive(Debug, Clone, Display, Deserialize, Serialize)]
pub struct OnGround {
    pub sim_on_ground: bool,
}

#[derive(Debug, Clone, Display, Deserialize, Serialize)]
pub struct IsParked {
    pub is_parked: bool,
}

// #[derive(Debug, Display, Clone, Deserialize, Serialize)]
// #[display("[{icao}] {lat} {lon} {alt}")]
// pub struct Airport {
//     pub icao: String,
//     pub region: String,
//     pub lat: f64,
//     pub lon: f64,
//     pub alt: f64,
// }

impl PartialEq for SimConnectPacket {
    fn eq(&self, other: &Self) -> bool {
        matches!(
            (self, other),
            (Self::AtcID(_), Self::AtcID(_)) | (Self::Fuel(_), Self::Fuel(_))
        )
    }
}
impl Eq for SimConnectPacket {}

#[derive(Debug, Display, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum Packets {
    System(SystemPacket),
    SimConnect(SimConnectPacket),
}

impl Packet for Packets {}

pub trait Packet: Serialize + de::DeserializeOwned + Sized {
    fn encode(&self) -> Result<Vec<u8>, anyhow::Error> {
        use flate2::write::GzEncoder;
        use flate2::Compression;
        use std::io::Write;

        let encoded = bincode::serialize(&self)?;
        let mut compressor = GzEncoder::new(Vec::new(), Compression::best());
        compressor.write_all(&encoded)?;

        Ok(compressor.finish()?)
    }

    fn decode(data: &[u8]) -> Result<Self, anyhow::Error> {
        use flate2::read::GzDecoder;
        use std::io::Read;
        let mut decompressor = GzDecoder::new(data);
        let mut buffer = Vec::new();
        decompressor.read_to_end(&mut buffer)?;

        Ok(bincode::deserialize(&buffer)?)
    }
}

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

    #[test]
    fn it_works() {
        let msg = Packets::System(SystemPacket::Ping);

        let decoded = Packet::decode(&msg.encode().unwrap()).unwrap();

        assert_eq!(msg, decoded);
    }
}
