//! # CoreVM builder protocol (CoreVM-BP)
//!
//! This protocol is used by the builder to receive guest input from the monitor.
//!
//! ## How it works
//!
//! The monitor is expected to provide [input chunks](corevm_host::InputChunk) signed with [input
//! key](corevm_host::ExecEnv::input_key) to the builder. Each chunk contains a time slice of _all_
//! input streams recorded by the monitor. The number of chunks is 8 per slot, allowing the monitor
//! to optimise for network throughput and latency. If one of the real-time streams (video/audio) is
//! enabled in the service storage, the builder will wait for the [last
//! chunk](InputChunkMessage::last) in the slot to arrive before invoking the guest code.
//!
//! Each chunk is wrapped in an [`InputChunkMessage`] that specifies the slot in which the input was
//! produced and the service ID of the guest. The monitor is expected to provide chunks in
//! chronological order, i.e. input chunk indices must be unique and increasing.

use bytes::{Buf, Bytes};
use codec::{ConstEncodedLen, Decode, Encode, MaxEncodedLen};
use jam_types::{max_input, slot_period_sec, ServiceId, Slot};
use std::time::Duration;

/// The number of chunks per [JAM time-slot](Slot).
pub const CHUNKS_PER_SLOT: u32 = 8;

/// The amount of time between two subsequent [input chunk indices](InputChunkIndex).
pub fn input_chunk_period() -> Duration {
	Duration::from_secs(slot_period_sec()) / CHUNKS_PER_SLOT
}

/// Returns maximum input chunk size in bytes.
pub fn max_input_chunk_size() -> u32 {
	max_input().div_ceil(CHUNKS_PER_SLOT)
}

/// This is universal unique index of an input chunk provided by the monitor to the builder. It is
/// more frequent than JAM time-slot to minmize latency (monitor can produce up to 12 MiB of
/// data per slot, and sending it all at once at the end of the slot might slow down the builder),
/// but not too frequent to make the overhead of signatures attached to each chunk negligible.
///
/// Equals one-eighth of a [time-slot](Slot).
#[derive(
	Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default, Encode, Decode, MaxEncodedLen,
)]
#[repr(transparent)]
pub struct InputChunkIndex(pub u32);

impl InputChunkIndex {
	/// Returns the corresponding time-slot.
	pub const fn slot(self) -> Slot {
		self.0 / CHUNKS_PER_SLOT
	}

	/// Returns `true` if this is the last chunk in its slot.
	pub const fn is_last_in_slot(self) -> bool {
		self.0 % CHUNKS_PER_SLOT == CHUNKS_PER_SLOT - 1
	}

	/// Returns the index of the first input chunk in the slot.
	///
	/// Doesn't check for overflow.
	pub const fn first_in_slot(slot: Slot) -> Self {
		Self(slot * CHUNKS_PER_SLOT)
	}

	/// Returns the index of the last input chunk in the slot.
	///
	/// Doesn't check for overflow.
	pub const fn last_in_slot(slot: Slot) -> Self {
		Self(slot * CHUNKS_PER_SLOT + (CHUNKS_PER_SLOT - 1))
	}
}

impl ConstEncodedLen for InputChunkIndex {}

impl core::fmt::Display for InputChunkIndex {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
		core::fmt::Display::fmt(&self.0, f)
	}
}

/// A chunk of input data.
///
/// Contains a time slice of _all_ input streams recorded by CoreVM monitor.
#[derive(Debug)]
pub struct InputChunkMessage {
	/// Service to which input chunk should be supplied.
	pub service_id: ServiceId,
	/// Index of the chunk.
	pub index: InputChunkIndex,
	/// Signed encoded [`InputChunk`](corevm_host::InputChunk).
	///
	/// The chunk is signed with [`ExecEnv::input_key`](corevm_host::ExecEnv::input_key).
	/// The signature is appended to the encoded chunk.
	pub body: Bytes,
}

impl InputChunkMessage {
	/// Encode all fields into the `output` except body.
	pub fn encode_header<O: codec::Output>(&self, output: &mut O) {
		self.service_id.encode_to(output);
		self.index.encode_to(output);
	}

	/// Decode message from the input.
	///
	/// The body is stored as a reference to the slice of the input.
	pub fn decode(mut input: Bytes) -> Result<Self, codec::Error> {
		let mut slice = &input[..];
		let service_id = ServiceId::decode(&mut slice)?;
		let index = InputChunkIndex::decode(&mut slice)?;
		let remaining_len = slice.len();
		input.advance(input.len() - remaining_len);
		let body = input;
		Ok(Self { service_id, index, body })
	}
}
