use crate::error::Reason;
use crate::frame::{Pseudo, StreamId};
use crate::proto::{Error, Open};

use http::{HeaderMap, Request, Response};

use std::fmt;

/// Either a Client or a Server
pub(crate) trait Peer {
    /// Message type polled from the transport
    type Poll: fmt::Debug;
    const NAME: &'static str;

    fn r#dyn() -> Dyn;

    fn is_server() -> bool;

    fn convert_poll_message(
        pseudo: Pseudo,
        fields: HeaderMap,
        stream_id: StreamId,
    ) -> Result<Self::Poll, Error>;

    fn is_local_init(id: StreamId) -> bool {
        assert!(!id.is_zero());
        Self::is_server() == id.is_server_initiated()
    }
}

/// A dynamic representation of `Peer`.
///
/// This is used internally to avoid incurring a generic on all internal types.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) enum Dyn {
    Client,
    Server,
}

#[derive(Debug)]
pub enum PollMessage {
    Client(Response<()>),
    Server(Request<()>),
}

// ===== impl Dyn =====

impl Dyn {
    pub fn is_server(&self) -> bool {
        *self == Dyn::Server
    }

    pub fn is_local_init(&self, id: StreamId) -> bool {
        assert!(!id.is_zero());
        self.is_server() == id.is_server_initiated()
    }

    pub fn convert_poll_message(
        &self,
        pseudo: Pseudo,
        fields: HeaderMap,
        stream_id: StreamId,
    ) -> Result<PollMessage, Error> {
        if self.is_server() {
            crate::server::Peer::convert_poll_message(pseudo, fields, stream_id)
                .map(PollMessage::Server)
        } else {
            crate::client::Peer::convert_poll_message(pseudo, fields, stream_id)
                .map(PollMessage::Client)
        }
    }

    /// Returns true if the remote peer can initiate a stream with the given ID.
    pub fn ensure_can_open(&self, id: StreamId, mode: Open) -> Result<(), Error> {
        if self.is_server() {
            // Ensure that the ID is a valid client initiated ID
            if mode.is_push_promise() || !id.is_client_initiated() {
                proto_err!(conn: "cannot open stream {:?} - not client initiated", id);
                return Err(Error::library_go_away(Reason::PROTOCOL_ERROR));
            }

            Ok(())
        } else {
            // Ensure that the ID is a valid server initiated ID
            if !mode.is_push_promise() || !id.is_server_initiated() {
                proto_err!(conn: "cannot open stream {:?} - not server initiated", id);
                return Err(Error::library_go_away(Reason::PROTOCOL_ERROR));
            }

            Ok(())
        }
    }
}
