use std::io::{Read, Write};
use mcproto_codec::{
error::{CodecError, CodecKind, InvalidEncodingReason},
varint::{VarIntRead, VarIntWrite},
};
use crate::{
GameProfile, GameProfileProperties, GameProfileUsername, Identifier, PrefixedOptional,
ProtocolEnum, TypeCodec, TypeStructCodec, Uuid, VarInt,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash, TypeStructCodec)]
#[type_struct_codec(kind = PartialProfile)]
pub struct PartialProfile {
pub username: PrefixedOptional<GameProfileUsername>,
pub uuid: PrefixedOptional<Uuid>,
pub properties: GameProfileProperties,
}
impl Default for PartialProfile {
fn default() -> Self {
Self {
username: PrefixedOptional::none(),
uuid: PrefixedOptional::none(),
properties: GameProfileProperties::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, ProtocolEnum)]
#[protocol_enum(repr = VarInt)]
pub enum SkinModel {
#[default]
Wide = 0,
Slim = 1,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ResolvableProfileData {
Partial(PartialProfile),
Complete(GameProfile),
}
impl From<PartialProfile> for ResolvableProfileData {
fn from(value: PartialProfile) -> Self {
Self::Partial(value)
}
}
impl From<GameProfile> for ResolvableProfileData {
fn from(value: GameProfile) -> Self {
Self::Complete(value)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ResolvableProfile {
pub profile: ResolvableProfileData,
pub body: PrefixedOptional<Identifier>,
pub cape: PrefixedOptional<Identifier>,
pub elytra: PrefixedOptional<Identifier>,
pub model: PrefixedOptional<SkinModel>,
}
impl ResolvableProfile {
#[must_use]
pub const fn new(profile: ResolvableProfileData) -> Self {
Self {
profile,
body: PrefixedOptional::none(),
cape: PrefixedOptional::none(),
elytra: PrefixedOptional::none(),
model: PrefixedOptional::none(),
}
}
#[must_use]
pub const fn partial(profile: PartialProfile) -> Self {
Self::new(ResolvableProfileData::Partial(profile))
}
#[must_use]
pub const fn complete(profile: GameProfile) -> Self {
Self::new(ResolvableProfileData::Complete(profile))
}
}
impl From<PartialProfile> for ResolvableProfile {
fn from(value: PartialProfile) -> Self {
Self::partial(value)
}
}
impl From<GameProfile> for ResolvableProfile {
fn from(value: GameProfile) -> Self {
Self::complete(value)
}
}
impl TypeCodec for ResolvableProfile {
fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
match &self.profile {
ResolvableProfileData::Partial(value) => {
writer
.write_varint(0)
.map_err(|error| error.with_context(CodecKind::ResolvableProfile))?;
value
.encode(writer)
.map_err(|error| error.with_context(CodecKind::ResolvableProfile))?;
}
ResolvableProfileData::Complete(value) => {
writer
.write_varint(1)
.map_err(|error| error.with_context(CodecKind::ResolvableProfile))?;
value
.encode(writer)
.map_err(|error| error.with_context(CodecKind::ResolvableProfile))?;
}
}
self.body
.encode(writer)
.map_err(|error| error.with_context(CodecKind::ResolvableProfile))?;
self.cape
.encode(writer)
.map_err(|error| error.with_context(CodecKind::ResolvableProfile))?;
self.elytra
.encode(writer)
.map_err(|error| error.with_context(CodecKind::ResolvableProfile))?;
self.model
.encode(writer)
.map_err(|error| error.with_context(CodecKind::ResolvableProfile))
}
fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
let (kind, kind_size) = reader
.read_varint_with_size()
.map_err(|error| error.with_context(CodecKind::ResolvableProfile))?;
let profile = match kind {
0 => PartialProfile::decode(reader)
.map(ResolvableProfileData::Partial)
.map_err(|error| error.with_context(CodecKind::ResolvableProfile))?,
1 => GameProfile::decode(reader)
.map(ResolvableProfileData::Complete)
.map_err(|error| error.with_context(CodecKind::ResolvableProfile))?,
value => {
return Err(CodecError::invalid_encoding(
CodecKind::ResolvableProfile,
kind_size,
InvalidEncodingReason::InvalidEnumValue {
value: i128::from(value),
},
));
}
};
Ok(Self {
profile,
body: PrefixedOptional::decode(reader)
.map_err(|error| error.with_context(CodecKind::ResolvableProfile))?,
cape: PrefixedOptional::decode(reader)
.map_err(|error| error.with_context(CodecKind::ResolvableProfile))?,
elytra: PrefixedOptional::decode(reader)
.map_err(|error| error.with_context(CodecKind::ResolvableProfile))?,
model: PrefixedOptional::decode(reader)
.map_err(|error| error.with_context(CodecKind::ResolvableProfile))?,
})
}
}