use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use vecdb::{Formattable, Pco};

use crate::{TxIndex, Vout};

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default, JsonSchema, Hash, Pco)]
pub struct OutPoint(u64);

impl OutPoint {
    pub const COINBASE: Self = Self(u64::MAX);

    pub fn new(tx_index: TxIndex, vout: Vout) -> Self {
        let tx_index_bits = u64::from(tx_index) << 32;
        let vout_bits = u64::from(vout);
        Self(tx_index_bits | vout_bits)
    }

    #[inline(always)]
    pub fn tx_index(self) -> TxIndex {
        TxIndex::from((self.0 >> 32) as u32)
    }

    #[inline(always)]
    pub fn vout(self) -> Vout {
        if self.is_coinbase() {
            return Vout::MAX;
        }
        Vout::from(self.0 as u32)
    }

    #[inline(always)]
    pub fn is_coinbase(self) -> bool {
        self == Self::COINBASE
    }

    #[inline(always)]
    pub fn is_not_coinbase(self) -> bool {
        self != Self::COINBASE
    }
}

impl std::fmt::Display for OutPoint {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "tx_index: {}, vout: {}", self.tx_index(), self.vout())
    }
}

impl Formattable for OutPoint {
    fn write_to(&self, buf: &mut Vec<u8>) {
        use std::fmt::Write;
        let mut s = String::new();
        write!(s, "{}", self).unwrap();
        buf.extend_from_slice(s.as_bytes());
    }

    fn fmt_csv(&self, f: &mut String) -> std::fmt::Result {
        let start = f.len();
        self.fmt_into(f);
        if f.as_bytes()[start..].contains(&b',') {
            f.insert(start, '"');
            f.push('"');
        }
        Ok(())
    }

    fn fmt_json(&self, buf: &mut Vec<u8>) {
        buf.push(b'"');
        self.write_to(buf);
        buf.push(b'"');
    }
}

impl Serialize for OutPoint {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        use serde::ser::SerializeStruct;
        let mut state = serializer.serialize_struct("OutPoint", 2)?;
        state.serialize_field("tx_index", &self.tx_index())?;
        state.serialize_field("vout", &self.vout())?;
        state.end()
    }
}

impl<'de> Deserialize<'de> for OutPoint {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct Helper {
            tx_index: TxIndex,
            vout: Vout,
        }
        let h = Helper::deserialize(deserializer)?;
        Ok(Self::new(h.tx_index, h.vout))
    }
}
