//! Asset transfer state shared by HTTP and LLUDP pipelines.

#![allow(clippy::missing_errors_doc, clippy::must_use_candidate)]

use crate::{
    ChannelType, Error, ImageCodec, ImageType, Simulator, SourceType, StatusCode, TargetType,
    TransferError,
};
use libremetaverse_types::compat::SortedList;
use libremetaverse_types::{AssetType, UUID};
use std::collections::HashMap;
use std::sync::RwLock;
use std::time::Instant;

#[derive(Clone, Debug)]
pub struct Transfer {
    pub asset_data: Vec<u8>,
    pub asset_type: AssetType,
    pub id: UUID,
    pub size: i32,
    pub success: bool,
    pub transferred: i32,
    pub(crate) last_packet: Instant,
}
impl Transfer {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            asset_data: Vec::new(),
            asset_type: AssetType::Unknown,
            id: UUID::zero(),
            size: 0,
            success: false,
            transferred: 0,
            last_packet: Instant::now(),
        })
    }
    pub fn time_since_last_packet(&self) -> i32 {
        i32::try_from(self.last_packet.elapsed().as_millis()).unwrap_or(i32::MAX)
    }
    pub fn set_time_since_last_packet(&mut self, value: i32) {
        self.last_packet = Instant::now()
            .checked_sub(std::time::Duration::from_millis(
                u64::try_from(value.max(0)).unwrap_or(0),
            ))
            .unwrap_or_else(Instant::now);
    }
}

#[derive(Debug)]
pub struct AssetDownload {
    pub asset_id: UUID,
    pub channel: ChannelType,
    pub priority: f32,
    pub simulator: Option<Simulator>,
    pub source: SourceType,
    pub status: StatusCode,
    pub target: TargetType,
    pub next_packet: i32,
    pub out_of_order_packets: RwLock<HashMap<i32, Vec<u8>>>,
    pub base: Transfer,
}
impl AssetDownload {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            asset_id: UUID::zero(),
            channel: ChannelType::Asset,
            priority: 0.0,
            simulator: None,
            source: SourceType::Asset,
            status: StatusCode::Unknown,
            target: TargetType::Unknown,
            next_packet: 0,
            out_of_order_packets: RwLock::new(HashMap::new()),
            base: Transfer::new()?,
        })
    }
}

#[derive(Debug)]
pub struct ImageDownload {
    pub codec: ImageCodec,
    pub discard_level: i32,
    pub image_type: ImageType,
    pub packet_count: u16,
    pub packets_seen: SortedList<u16, u16>,
    pub priority: f32,
    pub simulator: Option<Simulator>,
    pub base: Transfer,
}

impl ImageDownload {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            codec: ImageCodec::Invalid,
            discard_level: 0,
            image_type: ImageType::Normal,
            packet_count: 0,
            packets_seen: SortedList(std::collections::BTreeMap::new()),
            priority: 0.0,
            simulator: None,
            base: Transfer::new()?,
        })
    }
}

#[derive(Clone, Debug)]
pub struct AssetUpload {
    pub asset_id: UUID,
    pub packet_num: u32,
    pub type_: AssetType,
    pub xfer_id: u64,
    pub base: Transfer,
}
impl AssetUpload {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            asset_id: UUID::zero(),
            packet_num: 0,
            type_: AssetType::Unknown,
            xfer_id: 0,
            base: Transfer::new()?,
        })
    }
}

#[derive(Clone, Debug)]
pub struct AssetUploadEventArgs {
    upload: AssetUpload,
}
impl AssetUploadEventArgs {
    pub fn new(upload: AssetUpload) -> Result<Self, Error> {
        Ok(Self { upload })
    }
    pub fn upload(&self) -> AssetUpload {
        self.upload.clone()
    }
}

#[derive(Clone, Debug)]
pub struct ImageReceiveProgressEventArgs {
    image_id: UUID,
    received: i32,
    total: i32,
}

impl ImageReceiveProgressEventArgs {
    pub fn new(image_id: UUID, received: i32, total: i32) -> Result<Self, Error> {
        if received < 0 || total < 0 || received > total {
            return Err(Error::Argument);
        }
        Ok(Self {
            image_id,
            received,
            total,
        })
    }
    pub const fn image_id(&self) -> UUID {
        self.image_id
    }
    pub const fn received(&self) -> i32 {
        self.received
    }
    pub const fn total(&self) -> i32 {
        self.total
    }
}

#[derive(Clone, Debug)]
pub struct InitiateDownloadEventArgs {
    sim_filename: String,
    viewer_filename: String,
}

impl InitiateDownloadEventArgs {
    pub fn new(sim_filename: String, viewer_filename: String) -> Result<Self, Error> {
        if sim_filename.as_bytes().contains(&0) || viewer_filename.as_bytes().contains(&0) {
            return Err(Error::Argument);
        }
        Ok(Self {
            sim_filename,
            viewer_filename,
        })
    }
    pub fn sim_file_name(&self) -> String {
        self.sim_filename.clone()
    }
    pub fn viewer_file_name(&self) -> String {
        self.viewer_filename.clone()
    }
}

#[derive(Clone, Debug)]
pub struct XferDownload {
    pub error: TransferError,
    pub filename: String,
    pub packet_num: u32,
    pub v_file_id: UUID,
    pub xfer_id: u64,
    pub base: Transfer,
}

impl XferDownload {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            error: TransferError::None,
            filename: String::new(),
            packet_num: 0,
            v_file_id: UUID::zero(),
            xfer_id: 0,
            base: Transfer::new()?,
        })
    }
}

#[derive(Clone, Debug)]
pub struct XferReceivedEventArgs {
    xfer: XferDownload,
}
impl XferReceivedEventArgs {
    pub fn new(xfer: XferDownload) -> Result<Self, Error> {
        Ok(Self { xfer })
    }
    pub fn xfer(&self) -> XferDownload {
        self.xfer.clone()
    }
}
