// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Protobuf service support for mesh types.

pub use grpc::Code;
pub use grpc::Status;
use mesh::message::Message;
use mesh::payload::protobuf::MessageReader;
use mesh::payload::protobuf::MessageSizer;
use mesh::payload::protobuf::MessageWriter;
use mesh::payload::DefaultEncoding;
use mesh::payload::MessageDecode;
use mesh::payload::Result;
use mesh::payload::Upcast;
use mesh::resource::Resource;
use thiserror::Error;

mod grpc {
    // Generated types use these crates, reference them here to ensure they are
    // not removed by automated tooling.
    use prost as _;
    use prost_types as _;

    include!(concat!(env!("OUT_DIR"), "/google.rpc.rs"));

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

    impl std::error::Error for Code {}
}

/// A generic RPC value.
///
/// This type is designed to have the same encoding as specific RPC values as
/// generated by mesh_build.
#[doc(hidden)]
#[derive(mesh::MeshPayload)]
pub struct GenericRpc {
    #[mesh(1)]
    pub method: String,
    #[mesh(2)]
    pub data: Vec<u8>,
    #[mesh(3)]
    pub port: mesh::local_node::Port, // TODO: transparent mesh::OneshotSender<std::result::Result<Vec<u8>, Status>>,
}

/// A generic RPC value, using borrows instead of owning types.
#[derive(mesh::MeshPayload)]
struct GenericRpcView<'a> {
    #[mesh(1)]
    method: &'a str,
    #[mesh(2)]
    data: &'a [u8],
    #[mesh(3)]
    port: mesh::local_node::Port,
}

/// Trait for service-specific RPC requests.
///
/// The message encoding for this type must match that of `GenericRpc`.
pub trait ServiceRpc: mesh::MeshPayload + Upcast<GenericRpc> {
    /// The service name.
    const NAME: &'static str;
}

/// Used in generated code by mesh_build.
#[doc(hidden)]
pub fn write_rpc_message(
    mut writer: MessageWriter<'_, '_, Resource>,
    method: &str,
    port: mesh::OneshotSender<Message>,
) {
    writer.field(1).bytes(method.as_bytes());
    writer.field(3).resource(Resource::Port(port.into()));
}

/// Used in generated code by mesh_build.
#[doc(hidden)]
pub fn compute_size_rpc_message(mut sizer: MessageSizer<'_>, method: &str) {
    sizer.field(1).bytes(method.len());
    sizer.field(3).resource();
}

/// Used in generated code by mesh_build.
#[doc(hidden)]
pub fn read_rpc_message<'a>(
    reader: MessageReader<'a, '_, Resource>,
) -> Result<(&'a str, &'a [u8], mesh::OneshotSender<Message>)> {
    mesh::payload::inplace_none!(v: GenericRpcView<'_>);
    <GenericRpcView<'_> as DefaultEncoding>::Encoding::read_message(&mut v, reader)?;
    let v = v.take().expect("should be constructed");
    Ok((v.method, v.data, v.port.into()))
}

#[derive(Debug, Error)]
#[error("unknown method '{0}'")]
#[doc(hidden)]
pub struct UnknownMethod(pub String);
