use std::{
    collections::VecDeque,
    os::fd::{AsRawFd, RawFd},
    time::Duration,
};

use cross::io::Poll;
use uipc::{Channel, Receiver, Sender};

use crate::{
    error::Error,
    event::{Event, EventData},
    message::{ClientMessage, ServerMessage},
};

pub struct Connection {
    sender: Sender<ClientMessage>,
    receiver: Receiver<ServerMessage>,
    event_queue: VecDeque<Event>,
    poll: Poll,
    // poll: PollChannel,
    timeout: Duration,
}

impl Connection {
    pub fn new() -> Result<Self, Error> {
        let channel = Channel::connect(crate::CHANNEL_NAME)?;
        channel.set_nonblocking(true)?;
        let (sender, receiver) = channel.split();
        let timeout = Duration::from_secs(1);
        let mut poll = Poll::new()?;
        let event_queue = VecDeque::new();

        poll.add(&receiver)?;

        Ok(Self {
            sender,
            receiver,
            timeout,
            poll,
            event_queue,
        })
    }

    pub fn filter_events<T, F: Fn(Event) -> Result<T, Event>>(
        &mut self,
        predicate: F,
    ) -> Result<T, Error> {
        loop {
            let Some(_) = self.poll.wait(Some(self.timeout))? else {
                return Err(Error::CommunicationTimeout);
            };

            let (data, file, _) = self.receiver.receive_with_file_from()?;
            let ServerMessage::Event(data) = data;
            let event = Event { data, file };

            match predicate(event) {
                Ok(val) => break Ok(val),
                Err(ev) => {
                    self.event_queue.push_back(ev);
                }
            }
        }
    }

    pub fn poll_event(&mut self) -> Result<Option<Event>, Error> {
        if let Some(event) = self.event_queue.pop_front() {
            return Ok(Some(event));
        }

        match self.receiver.receive_with_file_from() {
            Ok((data, file, _)) => {
                let ServerMessage::Event(data) = data;
                Ok(Some(Event { data, file }))
            }
            Err(error) if error.is_would_block() => Ok(None),
            Err(error) => Err(error.into()),
        }
    }

    pub fn receive_event(&mut self) -> Result<Event, Error> {
        if let Some(event) = self.event_queue.pop_front() {
            return Ok(event);
        }

        loop {
            match self.poll.wait(Some(self.timeout))? {
                Some(_) => (),
                None => continue,
            }

            let (data, file, _) = self.receiver.receive_with_file_from()?;
            let ServerMessage::Event(data) = data;

            break Ok(Event { data, file });
        }
    }

    pub fn send(&mut self, message: &ClientMessage) -> Result<(), Error> {
        self.sender.send(message)?;
        Ok(())
    }

    pub fn send_with_file<F: AsRawFd>(
        &mut self,
        message: &ClientMessage,
        file: &F,
    ) -> Result<(), Error> {
        self.sender.send_with_file(message, file)?;
        Ok(())
    }

    pub fn connect(&mut self) -> Result<u32, Error> {
        self.sender.send(&ClientMessage::ClientHello)?;
        self.filter_events(|ev| match ev.data {
            EventData::ServerHello(id) => Ok(id),
            _ => Err(ev),
        })
    }
}

impl AsRawFd for Connection {
    fn as_raw_fd(&self) -> RawFd {
        self.poll.as_raw_fd()
    }
}
