#![allow(non_upper_case_globals)]

use {
    crate::{
        pipewire::{
            pw_con::PwCon,
            pw_mem::{PwMem, PwMemType},
            pw_object::{PwObject, PwObjectData},
            pw_parser::{PwParser, PwParserError},
            pw_pod::{SPA_DATA_DmaBuf, SPA_DATA_MemFd, SpaDataType},
        },
        utils::bitflags::BitflagsExt,
    },
    std::rc::Rc,
    thiserror::Error,
};

pub struct PwCore {
    pub data: PwObjectData,
    pub con: Rc<PwCon>,
}

pw_opcodes! {
    PwCoreMethods;

    Hello = 1,
    Sync = 2,
    Pong = 3,
    Error = 4,
    GetRegistry = 5,
    CreateObject = 6,
    Destroy = 7,
}

pw_opcodes! {
    PwCoreEvents;

    Info = 0,
    Done = 1,
    Ping = 2,
    Error = 3,
    RemoveId = 4,
    BoundId = 5,
    AddMem = 6,
    RemoveMem = 7,
}

pub const PW_CORE_VERSION: i32 = 3;

impl PwCore {
    pub fn handle_info(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> {
        let s1 = p1.read_struct()?;
        let mut p2 = s1.fields;
        let id = p2.read_int()?;
        let cookie = p2.read_int()?;
        let user_name = p2.read_string()?;
        let host_name = p2.read_string()?;
        let version_name = p2.read_string()?;
        let name = p2.read_string()?;
        let change_mask = p2.read_long()?;
        let dict = p2.read_dict_struct()?;
        log::info!(
            "info: id={id}, cookie={cookie}, user_name={user_name}, host_name={host_name}, version_name={version_name}, name={name}, change_mask={change_mask}"
        );
        log::info!("dict: {:#?}", dict);
        Ok(())
    }

    pub fn handle_done(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> {
        let s1 = p1.read_struct()?;
        let mut p2 = s1.fields;
        let id = p2.read_uint()?;
        let seq = p2.read_uint()?;
        if let Some(obj) = self.con.objects.get(&id)
            && obj.data().sync_id.get() <= seq
        {
            obj.data().sync_id.set(seq);
            obj.done();
        }
        Ok(())
    }

    pub fn handle_ping(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> {
        let s1 = p1.read_struct()?;
        let mut p2 = s1.fields;
        let id = p2.read_int()?;
        let seq = p2.read_int()?;
        self.con.send(self, PwCoreMethods::Pong, |f| {
            f.write_struct(|f| {
                f.write_int(id);
                f.write_int(seq);
            });
        });
        Ok(())
    }

    pub fn handle_error(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> {
        let s1 = p1.read_struct()?;
        let mut p2 = s1.fields;
        let id = p2.read_int()?;
        let seq = p2.read_int()?;
        let res = p2.read_int()?;
        let error = p2.read_string()?;
        log::info!("error: id={id}, seq={seq}, res={res}, error={error}");
        Ok(())
    }

    pub fn handle_remove_id(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> {
        let s1 = p1.read_struct()?;
        let mut p2 = s1.fields;
        let id = p2.read_uint()?;
        self.con.objects.remove(&id);
        self.con.ids.borrow_mut().release(id);
        Ok(())
    }

    pub fn handle_bound_id(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> {
        let s1 = p1.read_struct()?;
        let mut p2 = s1.fields;
        let id = p2.read_uint()?;
        let bound_id = p2.read_uint()?;
        if let Some(obj) = self.con.objects.get(&id) {
            obj.data().bound_id.set(Some(bound_id));
            obj.bound_id(bound_id);
        }
        Ok(())
    }

    pub fn handle_add_mem(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> {
        let s1 = p1.read_struct()?;
        let mut p2 = s1.fields;
        let id = p2.read_uint()?;
        let ty = SpaDataType(p2.read_id()?);
        let fd = p2.read_fd()?;
        let flags = p2.read_int()?;
        let read = flags.contains(1);
        let write = flags.contains(2);
        let ty = match ty {
            SPA_DATA_MemFd => PwMemType::MemFd,
            SPA_DATA_DmaBuf => PwMemType::DmaBuf,
            _ => {
                log::error!("Ignoring unknown mem type {:?}", ty);
                return Ok(());
            }
        };
        self.con.mem.mems.set(
            id,
            Rc::new(PwMem {
                _ty: ty,
                read,
                write,
                fd,
            }),
        );
        Ok(())
    }

    pub fn handle_remove_mem(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> {
        let s1 = p1.read_struct()?;
        let mut p2 = s1.fields;
        let id = p2.read_uint()?;
        self.con.mem.mems.remove(&id);
        Ok(())
    }
}

pw_object_base! {
    PwCore, "core", PwCoreEvents;

    Info => handle_info,
    Done => handle_done,
    Ping => handle_ping,
    Error => handle_error,
    RemoveId => handle_remove_id,
    BoundId => handle_bound_id,
    AddMem => handle_add_mem,
    RemoveMem => handle_remove_mem,
}

impl PwObject for PwCore {}

#[derive(Debug, Error)]
pub enum PwCoreError {
    #[error(transparent)]
    PwParserError(#[from] PwParserError),
}
